index.blade.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. @extends('admin.layouts')
  2. @section('css')
  3. <link href="/assets/global/vendor/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
  4. <style>
  5. #swal2-content {
  6. display: grid !important;
  7. }
  8. .table a {
  9. text-decoration: none;
  10. }
  11. </style>
  12. @endsection
  13. @section('content')
  14. <div class="page-content container-fluid">
  15. <div class="panel">
  16. <div class="panel-heading">
  17. <h3 class="panel-title">{{ trans('admin.menu.node.list') }}</h3>
  18. @canany(['admin.node.geo', 'admin.node.create'])
  19. <div class="panel-actions btn-group">
  20. @can('admin.node.reload')
  21. @if ($nodeList->where('type', 4)->count())
  22. <button class="btn btn-info" type="button" onclick="reload(0)">
  23. <i class="icon wb-reload" id="reload_0" aria-hidden="true"></i> {{ trans('admin.node.reload_all') }}
  24. </button>
  25. @endif
  26. @endcan
  27. @can('admin.node.geo')
  28. <button class="btn btn-outline-default" type="button" onclick="refreshGeo(0)">
  29. <i class="icon wb-map" id="geo_0" aria-hidden="true"></i> {{ trans('admin.node.refresh_geo_all') }}
  30. </button>
  31. @endcan
  32. @can('admin.node.create')
  33. <a class="btn btn-primary" href="{{ route('admin.node.create') }}">
  34. <i class="icon wb-plus"></i> {{ trans('common.add') }}
  35. </a>
  36. @endcan
  37. </div>
  38. @endcan
  39. </div>
  40. <div class="panel-body">
  41. <table class="text-md-center" data-toggle="table" data-mobile-responsive="true">
  42. <thead class="thead-default">
  43. <tr>
  44. <th> ID</th>
  45. <th> {{ trans('model.common.type') }}</th>
  46. <th> {{ trans('model.node.name') }}</th>
  47. <th> {{ trans('model.node.domain') }}</th>
  48. <th> IP</th>
  49. <th> {{ trans('model.node.static') }}</th>
  50. <th> {{ trans('model.node.online_user') }}</th>
  51. <th> {{ trans('model.node.data_consume') }}</th>
  52. <th> {{ trans('model.node.data_rate') }}</th>
  53. <th> {{ trans('model.common.extend') }}</th>
  54. <th> {{ trans('common.status.attribute') }}</th>
  55. <th> {{ trans('common.action') }}</th>
  56. </tr>
  57. </thead>
  58. <tbody>
  59. @foreach ($nodeList as $node)
  60. <tr>
  61. <td> {{ $node->id }} </td>
  62. <td> {{ $node->type_label }} </td>
  63. <td> {{ $node->name }} </td>
  64. <td> {{ $node->server }} </td>
  65. <td> {{ $node->is_ddns ? trans('model.node.ddns') : $node->ip }} </td>
  66. <td> {{ $node->uptime ?: '-' }} </td>
  67. <td> {{ $node->online_users ?: '-' }} </td>
  68. <td> {{ $node->transfer }} </td>
  69. <td> {{ $node->traffic_rate }} </td>
  70. <td>
  71. @isset($node->profile['passwd'])
  72. {{-- 单端口 --}}
  73. <span class="badge badge-lg badge-info"><i class="fa-solid fa-1" aria-hidden="true"></i></span>
  74. @endisset
  75. @if ($node->is_display === 0)
  76. {{-- 节点完全不可见 --}}
  77. <span class="badge badge-lg badge-danger"><i class="icon wb-eye-close" aria-hidden="true"></i></span>
  78. @elseif($node->is_display === 1)
  79. {{-- 节点只在页面中显示 --}}
  80. <span class="badge badge-lg badge-danger"><i class="fa-solid fa-link-slash" aria-hidden="true"></i></span>
  81. @elseif($node->is_display === 2)
  82. {{-- 节点只可被订阅到 --}}
  83. <span class="badge badge-lg badge-danger"><i class="fa-solid fa-store-slash" aria-hidden="true"></i></span>
  84. @endif
  85. @if ($node->ip)
  86. <span class="badge badge-md badge-info"><i class="fa-solid fa-4" aria-hidden="true"></i></span>
  87. @endif
  88. @if ($node->ipv6)
  89. <span class="badge badge-md badge-info"><i class="fa-solid fa-6" aria-hidden="true"></i></span>
  90. @endif
  91. </td>
  92. <td>
  93. @if ($node->isOnline)
  94. @if ($node->status)
  95. {{ $node->load }}
  96. @else
  97. <i class="yellow-700 icon icon-spin fa-solid fa-gear" aria-hidden="true"></i>
  98. @endif
  99. @else
  100. @if ($node->status)
  101. <i class="red-600 fa-solid fa-gear" aria-hidden="true"></i>
  102. @else
  103. <i class="red-600 fa-solid fa-handshake-simple-slash" aria-hidden="true"></i>
  104. @endif
  105. @endif
  106. </td>
  107. <td>
  108. @canany(['admin.node.edit', 'admin.node.clone', 'admin.node.destroy', 'admin.node.monitor', 'admin.node.geo',
  109. 'admin.node.check', 'admin.node.reload'])
  110. <button class="btn btn-primary dropdown-toggle" data-boundary="viewport" data-toggle="dropdown" type="button"
  111. aria-expanded="false">
  112. <i class="icon wb-wrench" aria-hidden="true"></i>
  113. </button>
  114. <div class="dropdown-menu" role="menu">
  115. @can('admin.node.edit')
  116. <a class="dropdown-item" href="{{ route('admin.node.edit', [$node->id, 'page' => Request::query('page', 1)]) }}"
  117. role="menuitem">
  118. <i class="icon wb-edit" aria-hidden="true"></i> {{ trans('common.edit') }}
  119. </a>
  120. @endcan
  121. @can('admin.node.clone')
  122. <a class="dropdown-item" href="{{ route('admin.node.clone', $node) }}" role="menuitem">
  123. <i class="icon wb-copy" aria-hidden="true"></i> {{ trans('admin.clone') }}
  124. </a>
  125. @endcan
  126. @can('admin.node.destroy')
  127. <a class="dropdown-item red-700" href="javascript:delNode('{{ $node->id }}', '{{ $node->name }}')" role="menuitem">
  128. <i class="icon wb-trash" aria-hidden="true"></i> {{ trans('common.delete') }}
  129. </a>
  130. @endcan
  131. @can('admin.node.monitor')
  132. <a class="dropdown-item" href="{{ route('admin.node.monitor', $node) }}" role="menuitem">
  133. <i class="icon wb-stats-bars" aria-hidden="true"></i> {{ trans('admin.node.traffic_monitor') }}
  134. </a>
  135. @endcan
  136. <hr />
  137. @can('admin.node.geo')
  138. <a class="dropdown-item" href="javascript:refreshGeo('{{ $node->id }}')" role="menuitem">
  139. <i class="icon wb-map" id="geo{{ $node->id }}" aria-hidden="true"></i> {{ trans('admin.node.refresh_geo') }}
  140. </a>
  141. @endcan
  142. @can('admin.node.check')
  143. <a class="dropdown-item" href="javascript:checkNode('{{ $node->id }}')" role="menuitem">
  144. <i class="icon wb-signal" id="node_{{ $node->id }}" aria-hidden="true"></i>
  145. {{ trans('admin.node.connection_test') }}
  146. </a>
  147. @endcan
  148. @if ($node->type === 4)
  149. @can('admin.node.reload')
  150. <hr />
  151. <a class="dropdown-item" href="javascript:reload('{{ $node->id }}')" role="menuitem">
  152. <i class="icon wb-reload" id="reload_{{ $node->id }}" aria-hidden="true"></i> {{ trans('admin.node.reload') }}
  153. </a>
  154. @endcan
  155. @endif
  156. </div>
  157. @endcan
  158. </td>
  159. </tr>
  160. @if (count($node->childNodes))
  161. @foreach ($node->childNodes as $childNode)
  162. <tr class="bg-blue-grey-200 grey-700 table-borderless frontlin">
  163. <td></td>
  164. <td><i class="float-left fa-solid fa-right-left" aria-hidden="true"></i>
  165. <strong>{{ trans('model.node.transfer') }}</strong>
  166. </td>
  167. <td> {{ $childNode->name }} </td>
  168. <td> {{ $childNode->server }} </td>
  169. <td> {{ $childNode->is_ddns ? trans('model.node.ddns') : $childNode->ip }} </td>
  170. <td colspan="2">
  171. @if ($childNode->is_display === 0)
  172. {{-- 节点完全不可见 --}}
  173. <span class="badge badge-lg badge-danger"><i class="icon wb-eye-close" aria-hidden="true"></i></span>
  174. @elseif($childNode->is_display === 1)
  175. {{-- 节点只在页面中显示 --}}
  176. <span class="badge badge-lg badge-danger"><i class="fa-solid fa-link-slash" aria-hidden="true"></i></span>
  177. @elseif($childNode->is_display === 2)
  178. {{-- 节点只可被订阅到 --}}
  179. <span class="badge badge-lg badge-danger"><i class="fa-solid fa-store-slash" aria-hidden="true"></i></span>
  180. @endif
  181. </td>
  182. <td colspan="2">
  183. @if (!$childNode->status || !$node->status)
  184. <i class="red-600 fa-solid fa-handshake-simple-slash" aria-hidden="true"></i>
  185. @endif
  186. </td>
  187. <td colspan="3">
  188. @canany(['admin.node.edit', 'admin.node.clone', 'admin.node.destroy', 'admin.node.monitor', 'admin.node.geo',
  189. 'admin.node.check'])
  190. <button class="btn btn-primary dropdown-toggle" data-boundary="viewport" data-toggle="dropdown" type="button"
  191. aria-expanded="false">
  192. <i class="icon wb-wrench" aria-hidden="true"></i>
  193. </button>
  194. <div class="dropdown-menu" role="menu">
  195. @can('admin.node.edit')
  196. <a class="dropdown-item"
  197. href="{{ route('admin.node.edit', [$childNode->id, 'page' => Request::query('page', 1)]) }}" role="menuitem">
  198. <i class="icon wb-edit" aria-hidden="true"></i> {{ trans('common.edit') }}
  199. </a>
  200. @endcan
  201. @can('admin.node.clone')
  202. <a class="dropdown-item" href="{{ route('admin.node.clone', $childNode) }}" role="menuitem">
  203. <i class="icon wb-copy" aria-hidden="true"></i> {{ trans('admin.clone') }}
  204. </a>
  205. @endcan
  206. @can('admin.node.destroy')
  207. <a class="dropdown-item red-700" href="javascript:delNode('{{ $childNode->id }}', '{{ $childNode->name }}')"
  208. role="menuitem">
  209. <i class="icon wb-trash" aria-hidden="true"></i> {{ trans('common.delete') }}
  210. </a>
  211. @endcan
  212. @can('admin.node.monitor')
  213. <a class="dropdown-item" href="{{ route('admin.node.monitor', $childNode) }}" role="menuitem">
  214. <i class="icon wb-stats-bars" aria-hidden="true"></i> {{ trans('admin.node.traffic_monitor') }}
  215. </a>
  216. @endcan
  217. <hr />
  218. @can('admin.node.geo')
  219. <a class="dropdown-item" href="javascript:refreshGeo('{{ $childNode->id }}')" role="menuitem">
  220. <i class="icon wb-map" id="geo_{{ $childNode->id }}" aria-hidden="true"></i>
  221. {{ trans('admin.node.refresh_geo') }}
  222. </a>
  223. @endcan
  224. @can('admin.node.check')
  225. <a class="dropdown-item" href="javascript:checkNode('{{ $childNode->id }}')" role="menuitem">
  226. <i class="icon wb-signal" id="node_{{ $childNode->id }}" aria-hidden="true"></i>
  227. {{ trans('admin.node.connection_test') }}
  228. </a>
  229. @endcan
  230. </div>
  231. @endcan
  232. </td>
  233. </tr>
  234. @endforeach
  235. @endif
  236. @endforeach
  237. </tbody>
  238. </table>
  239. </div>
  240. <div class="panel-footer">
  241. <div class="row">
  242. <div class="col-sm-4">
  243. {!! trans('admin.node.counts', ['num' => $nodeList->total()]) !!}
  244. </div>
  245. <div class="col-sm-8">
  246. <nav class="Page navigation float-right">
  247. {{ $nodeList->links() }}
  248. </nav>
  249. </div>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. @endsection
  255. @section('javascript')
  256. <script src="/assets/global/vendor/bootstrap-table/bootstrap-table.min.js"></script>
  257. <script src="/assets/global/vendor/bootstrap-table/extensions/mobile/bootstrap-table-mobile.min.js"></script>
  258. <script>
  259. @can('admin.node.check')
  260. // 节点连通性测试
  261. function checkNode(id) {
  262. $.ajax({
  263. method: 'POST',
  264. url: '{{ route('admin.node.check', '') }}/' + id,
  265. data: {
  266. _token: '{{ csrf_token() }}'
  267. },
  268. beforeSend: function() {
  269. $('#node_' + id).removeClass('wb-signal').addClass('wb-loop icon-spin');
  270. },
  271. success: function(ret) {
  272. if (ret.status === 'success') {
  273. let str = '';
  274. for (let i in ret.message) {
  275. str += '<tr><td>' + i + '</td><td>' + ret.message[i][0] + '</td><td>' + ret.message[i][1] +
  276. '</td></tr>';
  277. }
  278. swal.fire({
  279. title: ret.title,
  280. icon: 'info',
  281. html: '<table class="my-20"><thead class="thead-default"><tr><th> IP </th><th> ICMP </th> <th> TCP </th></thead><tbody>' +
  282. str + '</tbody></table>',
  283. showConfirmButton: false,
  284. });
  285. } else {
  286. swal.fire({
  287. title: ret.title,
  288. text: ret.message,
  289. icon: 'error'
  290. });
  291. }
  292. },
  293. complete: function() {
  294. $('#node_' + id).removeClass('wb-loop icon-spin').addClass('wb-signal');
  295. },
  296. });
  297. }
  298. @endcan
  299. @can('admin.node.reload')
  300. // 发送节点重载请求
  301. function reload(id) {
  302. swal.fire({
  303. text: '{{ trans('admin.node.reload_confirm') }}',
  304. icon: 'question',
  305. showCancelButton: true,
  306. cancelButtonText: '{{ trans('common.close') }}',
  307. confirmButtonText: '{{ trans('common.confirm') }}',
  308. }).then((result) => {
  309. if (result.value) {
  310. $.ajax({
  311. method: 'POST',
  312. url: '{{ route('admin.node.reload', '') }}/' + id,
  313. data: {
  314. _token: '{{ csrf_token() }}'
  315. },
  316. beforeSend: function() {
  317. $('#reload_' + id).removeClass('wb-reload').addClass('wb-loop icon-spin');
  318. },
  319. success: function(ret) {
  320. if (ret.status === 'success') {
  321. swal.fire({
  322. title: ret.message,
  323. icon: 'info',
  324. showConfirmButton: false
  325. });
  326. } else {
  327. swal.fire({
  328. title: ret.message,
  329. icon: 'error'
  330. });
  331. }
  332. },
  333. complete: function() {
  334. $('#reload_' + id).removeClass('wb-loop icon-spin').addClass('wb-reload');
  335. },
  336. });
  337. }
  338. });
  339. }
  340. @endcan
  341. @can('admin.node.geo')
  342. // 刷新节点地理信息
  343. function refreshGeo(id) {
  344. $.ajax({
  345. method: 'GET',
  346. url: '{{ route('admin.node.geo', '') }}/' + id,
  347. data: {
  348. _token: '{{ csrf_token() }}'
  349. },
  350. beforeSend: function() {
  351. $('#geo_' + id).removeClass('wb-map').addClass('wb-loop icon-spin');
  352. },
  353. success: function(ret) {
  354. if (ret.status === 'success') {
  355. swal.fire({
  356. title: ret.message,
  357. icon: 'info',
  358. showConfirmButton: false
  359. });
  360. } else {
  361. swal.fire({
  362. title: ret.message,
  363. icon: 'error'
  364. });
  365. }
  366. },
  367. complete: function() {
  368. $('#geo_' + id).removeClass('wb-loop icon-spin').addClass('wb-map');
  369. },
  370. });
  371. }
  372. @endcan
  373. @can('admin.node.destroy')
  374. // 删除节点
  375. function delNode(id, name) {
  376. swal.fire({
  377. title: '{{ trans('common.warning') }}',
  378. text: '{{ trans('admin.confirm.delete.0', ['attribute' => trans('model.node.attribute')]) }}' + name +
  379. '{{ trans('admin.confirm.delete.1') }}',
  380. icon: 'warning',
  381. showCancelButton: true,
  382. cancelButtonText: '{{ trans('common.close') }}',
  383. confirmButtonText: '{{ trans('common.confirm') }}',
  384. }).then((result) => {
  385. if (result.value) {
  386. $.ajax({
  387. method: 'DELETE',
  388. url: '{{ route('admin.node.destroy', '') }}/' + id,
  389. data: {
  390. _token: '{{ csrf_token() }}'
  391. },
  392. dataType: 'json',
  393. success: function(ret) {
  394. if (ret.status === 'success') {
  395. swal.fire({
  396. title: ret.message,
  397. icon: 'success',
  398. timer: 1000,
  399. showConfirmButton: false,
  400. }).then(() => window.location.reload());
  401. } else {
  402. swal.fire({
  403. title: ret.message,
  404. icon: 'error'
  405. }).then(() => window.location.reload());
  406. }
  407. },
  408. });
  409. }
  410. });
  411. }
  412. @endcan
  413. </script>
  414. @endsection