nodeList.blade.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. @extends('user.layouts')
  2. @section('css')
  3. <link href="/assets/global/fonts/font-awesome/css/all.min.css" rel="stylesheet">
  4. <link href="/assets/global/vendor/webui-popover/webui-popover.min.css" rel="stylesheet">
  5. <link href="/assets/global/vendor/jvectormap/jquery-jvectormap.min.css" rel="stylesheet">
  6. <style>
  7. .flag-icon-rounded {
  8. border-radius: 50%;
  9. background-size: cover;
  10. height: 100%;
  11. width: auto;
  12. aspect-ratio: 1 / 1;
  13. }
  14. </style>
  15. @endsection
  16. @section('content')
  17. <div class="page-content container-fluid">
  18. <div class="row">
  19. <div class="col-md-9">
  20. <div class="card card-inverse card-shadow bg-white map">
  21. <div class="card-block h-450">
  22. <div class="h-p100" id="world-map"></div>
  23. </div>
  24. </div>
  25. </div>
  26. <div class="col-md-3">
  27. <div class="row map">
  28. <div class="col-md-12">
  29. <div class="card card-block p-20 bg-indigo-500">
  30. <div class="counter counter-lg counter-inverse">
  31. <div class="counter-label text-uppercase font-size-16">{{ trans('user.account.level') }}</div>
  32. <div class="counter-number-group">
  33. <span class="counter-icon"><i class="icon wb-user-circle" aria-hidden="true"></i></span>
  34. <span class="counter-number ml-10">{{ auth()->user()->level }}</span>
  35. </div>
  36. <div class="counter-label text-uppercase font-size-16">{{ auth()->user()->level_name }}</div>
  37. </div>
  38. </div>
  39. </div>
  40. @if (auth()->user()->user_group_id)
  41. <div class="col-md-12">
  42. <div class="card card-block p-30 bg-indigo-500">
  43. <div class="counter counter-lg counter-inverse">
  44. <div class="counter-label text-uppercase font-size-16">{{ trans('user.account.group') }}</div>
  45. <div class="counter-number-group">
  46. <span class="counter-icon"><i class="icon wb-globe" aria-hidden="true"></i></span>
  47. <span class="counter-number ml-10">{{ auth()->user()->userGroup->name }}</span>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. @endif
  53. <div class="col-md-12">
  54. <div class="card card-block p-30 bg-indigo-500">
  55. <div class="counter counter-lg counter-inverse">
  56. <div class="counter-label text-uppercase font-size-16">{{ trans('user.account.speed_limit') }}</div>
  57. <div class="counter-number-group">
  58. <span class="counter-icon"><i class="icon wb-signal" aria-hidden="true"></i></span>
  59. <span class="counter-number ml-10">{{ auth()->user()->speed_limit ?: trans('common.unlimited') }}</span>
  60. </div>
  61. <div class="counter-label font-size-16">Mbps</div>
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. @foreach ($nodes as $node)
  68. <div class="col-xxl-3 col-xl-4 col-sm-6">
  69. <div class="card card-inverse card-shadow bg-white node-card">
  70. <div class="card-block p-30 row">
  71. <div class="col-3">
  72. <i class="fi fi-{{ $node->country_code }} flag-icon-rounded" aria-hidden="true"></i>
  73. </div>
  74. <div class="col-9 text-break text-right">
  75. <p class="font-size-20 blue-600">
  76. <span class="float-left badge badge-round badge-default">{{ $node->level_table->name }}</span>
  77. @if ($node->offline && !$node->relay_node_id)
  78. <i class="red-600 icon wb-warning" data-content="{{ trans('user.node.unstable') }}" data-trigger="hover"
  79. data-toggle="popover" data-placement="top"></i>
  80. @endif
  81. @if ($node->traffic_rate !== 1.0)
  82. <i class="green-600 icon wb-info-circle" data-content="{{ trans('user.node.rate', ['ratio' => $node->traffic_rate]) }}"
  83. data-trigger="hover" data-toggle="popover" data-placement="top"></i>
  84. @endif
  85. {{ $node->name }}
  86. </p>
  87. <blockquote>
  88. @foreach ($node->label_names->take(3) as $label_name)
  89. <span class="badge badge-lg badge-round badge-info">{{ $label_name }}</span>
  90. @endforeach
  91. @if ($node->label_names->count() > 3)
  92. <i class="icon wb-more-horizontal" data-content="{{ $node->label_names->join(', ') }}" data-trigger="hover"
  93. data-toggle="popover" data-placement="top"></i>
  94. @endif
  95. <br>
  96. {{ $node->description }}
  97. </blockquote>
  98. <div>
  99. <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','code')">
  100. <i class="fa-solid fa-code" id="code{{ $node->id }}"></i>
  101. </button>
  102. <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','qrcode')">
  103. <i class="fa-solid fa-qrcode" id="qrcode{{ $node->id }}"></i>
  104. </button>
  105. <button class="btn btn-sm btn-outline-info" onclick="getInfo('{{ $node->id }}','text')">
  106. <i class="fa-solid fa-list" id="text{{ $node->id }}"></i>
  107. </button>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. @endforeach
  114. </div>
  115. </div>
  116. @endsection
  117. @section('javascript')
  118. <script src="/assets/global/vendor/matchheight/jquery.matchHeight-min.js" type="text/javascript"></script>
  119. <script src="/assets/global/js/Plugin/matchheight.js" type="text/javascript"></script>
  120. <script src="/assets/custom/easy.qrcode.min.js" type="text/javascript"></script>
  121. <script src="/assets/global/js/Plugin/webui-popover.js" type="text/javascript"></script>
  122. <script src="/assets/global/vendor/jvectormap/jquery-jvectormap.min.js"></script>
  123. <script src="/assets/custom/maps/jquery-jvectormap-world-mill-cn.js"></script>
  124. <script type="text/javascript">
  125. $(function() {
  126. $("#world-map").vectorMap({
  127. map: "world_mill",
  128. scaleColors: ["#C8EEFF", "#0071A4"],
  129. normalizeFunction: "polynomial",
  130. zoomAnimate: true,
  131. hoverOpacity: 0.7,
  132. hoverColor: false,
  133. regionStyle: {
  134. initial: {
  135. fill: "#3E8EF7"
  136. },
  137. hover: {
  138. fill: "#589FFC"
  139. },
  140. selected: {
  141. fill: "#0B69E3"
  142. },
  143. selectedHover: {
  144. fill: "#589FFC"
  145. }
  146. },
  147. markerStyle: {
  148. initial: {
  149. r: 3,
  150. fill: "#FF4C52",
  151. "stroke-width": 0
  152. },
  153. hover: {
  154. r: 6,
  155. stroke: "#FF4C52",
  156. "stroke-width": 0
  157. }
  158. },
  159. backgroundColor: "#fff",
  160. markers: [
  161. @foreach ($nodesGeo as $geo => $name)
  162. {
  163. latLng: [{{ $geo }}],
  164. name: '{{ $name }}'
  165. },
  166. @endforeach
  167. ]
  168. });
  169. $(".node-card").matchHeight();
  170. $(".map").matchHeight();
  171. });
  172. function getInfo(id, type) {
  173. const oldClass = $(`#${type}${id}`).attr("class");
  174. const iconElement = $(`#${type}${id}`);
  175. ajaxPost(jsRoute('{{ route('node.show', 'PLACEHOLDER') }}', id), {
  176. type: type
  177. }, {
  178. beforeSend: function() {
  179. iconElement.removeClass().addClass("icon wb-loop icon-spin");
  180. },
  181. success: function(ret) {
  182. if (ret.status === "success") {
  183. switch (type) {
  184. case "code":
  185. swal.fire({
  186. html: "<textarea class=\"form-control\" rows=\"8\" readonly=\"readonly\">" + ret.data + "</textarea>" +
  187. "<a href=\"" + ret.data + '" class="btn btn-block btn-danger mt-4">{{ trans('common.open') }}' +
  188. ret.title + "</a>",
  189. showConfirmButton: false
  190. });
  191. break;
  192. case "qrcode":
  193. swal.fire({
  194. title: '{{ trans('user.scan_qrcode') }}',
  195. html: '<div id="qrcode"></div><button class="btn btn-block btn-outline-primary mt-4" onclick="Download()"> <i class="icon wb-download"></i> {{ trans('common.download') }}</button>',
  196. onBeforeOpen: () => {
  197. new QRCode(document.getElementById("qrcode"), {
  198. text: ret.data
  199. });
  200. },
  201. showConfirmButton: false
  202. });
  203. break;
  204. case "text":
  205. swal.fire({
  206. title: '{{ trans('user.node.info') }}',
  207. html: "<textarea class=\"form-control\" rows=\"12\" readonly=\"readonly\">" + ret.data + "</textarea>",
  208. showConfirmButton: false
  209. });
  210. break;
  211. default:
  212. swal.fire({
  213. title: ret.title,
  214. text: ret.data,
  215. icon: "error"
  216. });
  217. }
  218. }
  219. },
  220. complete: function() {
  221. iconElement.removeClass().addClass(oldClass);
  222. }
  223. });
  224. }
  225. function Download() {
  226. const canvas = document.getElementsByTagName("canvas")[0];
  227. canvas.toBlob((blob) => {
  228. let link = document.createElement("a");
  229. link.download = "qr.png";
  230. let reader = new FileReader();
  231. reader.readAsDataURL(blob);
  232. reader.onload = () => {
  233. link.href = reader.result;
  234. link.click();
  235. };
  236. }, "image/png");
  237. }
  238. </script>
  239. @endsection