index.blade.php 25 KB

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