index.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. // ==========================================
  3. // 1. BACKEND API (Για το Server Hub)
  4. // ==========================================
  5. if (isset($_GET['api'])) {
  6. header('Content-Type: application/json');
  7. $file = 'servers.json';
  8. if (!file_exists($file)) {
  9. file_put_contents($file, json_encode([]));
  10. }
  11. $data = json_decode(file_get_contents($file), true);
  12. // Προσθήκη νέου server
  13. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  14. $input = json_decode(file_get_contents('php://input'), true);
  15. $url = filter_var($input['url'] ?? '', FILTER_SANITIZE_URL);
  16. if ($url) {
  17. if (!preg_match("~^(?:f|ht)tps?://~i", $url)) $url = "http://" . $url;
  18. // Αφαίρεση του / στο τέλος αν υπάρχει για ομοιομορφία
  19. $url = rtrim($url, '/');
  20. $exists = false;
  21. foreach ($data as $server) {
  22. if ($server['url'] === $url) $exists = true;
  23. }
  24. if (!$exists) {
  25. $data[] = ['url' => $url];
  26. file_put_contents($file, json_encode($data));
  27. }
  28. echo json_encode(['success' => true]);
  29. } else {
  30. echo json_encode(['success' => false, 'error' => 'Invalid URL']);
  31. }
  32. exit;
  33. }
  34. // Ανάγνωση και Healthcheck
  35. $result = [];
  36. foreach ($data as $server) {
  37. $url = $server['url'];
  38. $ch = curl_init($url);
  39. curl_setopt($ch, CURLOPT_NOBODY, true);
  40. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  41. curl_setopt($ch, CURLOPT_TIMEOUT, 3);
  42. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  43. curl_exec($ch);
  44. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  45. curl_close($ch);
  46. $status = ($httpCode >= 200 && $httpCode < 400) ? 'online' : 'offline';
  47. $result[] = ['url' => $url, 'status' => $status];
  48. }
  49. echo json_encode($result);
  50. exit;
  51. }
  52. ?>
  53. <!DOCTYPE html>
  54. <html lang="el">
  55. <head>
  56. <meta charset="UTF-8">
  57. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  58. <title>Modern SpeedTest & Server Hub</title>
  59. <script src="speedtest.js"></script>
  60. <style>
  61. @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap');
  62. * { box-sizing: border-box; margin: 0; padding: 0; }
  63. body { background-color: #0b0c10; color: #c5c6c7; font-family: 'Poppins', sans-serif; display: flex; align-items: flex-start; justify-content: center; min-height: 100vh; padding: 40px 20px; }
  64. .container { background: #1f2833; padding: 40px 30px; border-radius: 24px; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6); text-align: center; width: 100%; max-width: 500px; }
  65. h1 { color: #66fcf1; font-size: 24px; margin-bottom: 30px; letter-spacing: 1px; text-transform: uppercase; }
  66. /* SpeedTest Metrics */
  67. .metrics { display: flex; justify-content: space-between; margin-bottom: 40px; gap: 15px; }
  68. .metric { background: #0b0c10; padding: 15px 10px; border-radius: 16px; flex: 1; box-shadow: inset 0 2px 5px rgba(0,0,0,0.5); transition: transform 0.3s ease, box-shadow 0.3s ease; }
  69. .metric.active-dl { box-shadow: inset 0 0 15px rgba(102, 252, 241, 0.3); border: 1px solid #45a29e; }
  70. .metric.active-ul { box-shadow: inset 0 0 15px rgba(195, 7, 63, 0.3); border: 1px solid #c3073f; }
  71. .metric span { display: block; font-size: 11px; text-transform: uppercase; color: #45a29e; letter-spacing: 1px; margin-bottom: 5px; }
  72. .metric strong { font-size: 22px; color: #ffffff; font-weight: 600; }
  73. .metric small { font-size: 12px; color: #66fcf1; margin-left: 2px; }
  74. /* Gauge */
  75. .gauge-container { position: relative; width: 220px; height: 220px; margin: 0 auto 40px; }
  76. .gauge { width: 100%; height: 100%; border-radius: 50%; background: conic-gradient(#1f2833 100%, #0b0c10 0); display: flex; align-items: center; justify-content: center; box-shadow: 0 0 30px rgba(102, 252, 241, 0.1); transition: background 0.1s linear; }
  77. .gauge-inner { width: 190px; height: 190px; background: #1f2833; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: inset 0 0 20px rgba(0,0,0,0.5); }
  78. .current-status { font-size: 14px; color: #45a29e; text-transform: uppercase; letter-spacing: 2px; margin-bottom: -5px; height: 20px; }
  79. .current-speed { font-size: 48px; font-weight: 600; color: #66fcf1; text-shadow: 0 0 10px rgba(102, 252, 241, 0.4); }
  80. .speed-unit { font-size: 14px; color: #c5c6c7; }
  81. /* Controls (Dropdown & Button) */
  82. .controls-group { margin-bottom: 30px; }
  83. select { width: 100%; padding: 14px; border-radius: 12px; background: #0b0c10; color: #66fcf1; border: 1px solid #45a29e; outline: none; font-family: 'Poppins', sans-serif; font-size: 14px; margin-bottom: 15px; cursor: pointer; appearance: none; text-align: center;}
  84. button { background: linear-gradient(135deg, #66fcf1, #45a29e); border: none; padding: 16px 50px; border-radius: 30px; color: #0b0c10; font-size: 18px; font-weight: 600; cursor: pointer; text-transform: uppercase; width: 100%; transition: all 0.3s ease; }
  85. button:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(102, 252, 241, 0.4); }
  86. button:disabled { background: #45a29e; opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }
  87. /* Hub Section */
  88. hr { border: 0; height: 1px; background: #45a29e; opacity: 0.3; margin: 30px 0; }
  89. .hub-section h3 { color: #45a29e; font-size: 16px; margin-bottom: 15px; text-transform: uppercase; }
  90. .input-group { display: flex; gap: 10px; margin-bottom: 20px; }
  91. .input-group input { flex: 1; padding: 12px; border-radius: 8px; border: 1px solid #0b0c10; background: #0b0c10; color: #fff; outline: none; }
  92. .input-group button { width: auto; margin: 0; padding: 12px 20px; font-size: 14px; border-radius: 8px; }
  93. .server-list { text-align: left; background: #0b0c10; padding: 15px; border-radius: 12px; max-height: 200px; overflow-y: auto; }
  94. .server-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #1f2833; }
  95. .server-item:last-child { border-bottom: none; }
  96. .server-url { font-size: 14px; color: #c5c6c7; word-break: break-all; }
  97. .status-badge { font-size: 12px; padding: 4px 8px; border-radius: 12px; text-transform: uppercase; font-weight: 600; }
  98. .status-online { background: rgba(46, 204, 113, 0.2); color: #2ecc71; }
  99. .status-offline { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
  100. /* Download Agent Section */
  101. .download-agent-box {
  102. background: #0b0c10;
  103. border: 1px dashed #45a29e;
  104. padding: 20px;
  105. border-radius: 12px;
  106. margin-bottom: 25px;
  107. text-align: center;
  108. }
  109. .download-agent-box p {
  110. font-size: 13px;
  111. color: #c5c6c7;
  112. margin-bottom: 15px;
  113. letter-spacing: 0.5px;
  114. }
  115. .download-btn {
  116. display: inline-block;
  117. background: transparent;
  118. color: #66fcf1;
  119. text-decoration: none;
  120. padding: 10px 25px;
  121. border: 1px solid #66fcf1;
  122. border-radius: 20px;
  123. font-size: 12px;
  124. text-transform: uppercase;
  125. letter-spacing: 1px;
  126. transition: all 0.3s ease;
  127. }
  128. .download-btn:hover {
  129. background: rgba(102, 252, 241, 0.1);
  130. box-shadow: 0 0 15px rgba(102, 252, 241, 0.3);
  131. transform: translateY(-2px);
  132. }
  133. </style>
  134. </head>
  135. <body>
  136. <div class="container">
  137. <h1>WiSpeed.awmn</h1>
  138. <div class="metrics">
  139. <div class="metric" id="box-ping"><span>Ping</span><strong id="ping-val">--</strong><small>ms</small></div>
  140. <div class="metric" id="box-dl"><span>Download</span><strong id="dl-val">--</strong><small>Mb/s</small></div>
  141. <div class="metric" id="box-ul"><span>Upload</span><strong id="ul-val">--</strong><small>Mb/s</small></div>
  142. </div>
  143. <div class="gauge-container">
  144. <div class="gauge" id="gauge">
  145. <div class="gauge-inner">
  146. <div class="current-status" id="status-text">Ready</div>
  147. <div class="current-speed" id="main-val">0</div>
  148. <div class="speed-unit" id="speed-unit">Mbps</div>
  149. </div>
  150. </div>
  151. </div>
  152. <div class="controls-group">
  153. <select id="server-select">
  154. <option value="local">Αυτός ο Server (Τοπικός)</option>
  155. </select>
  156. <button id="start-btn" onclick="startTest()">Έναρξη</button>
  157. </div>
  158. <hr>
  159. <div class="hub-section">
  160. <div class="download-agent-box">
  161. <p>Host and register your own WiSpeed Server</p>
  162. <a href="speedagent.tar.gz" class="download-btn">⬇ Download Agent</a>
  163. </div>
  164. <h3>Καταχώρηση Νέου Node</h3>
  165. <div class="input-group">
  166. <input type="text" id="new-server-url" placeholder="π.χ. nodeserver1.com">
  167. <button onclick="registerServer()">Προσθήκη</button>
  168. </div>
  169. <h3>Ενεργοί Servers (Healthcheck)</h3>
  170. <div class="server-list" id="server-list-container">
  171. <div class="server-item" style="justify-content: center; color: #888; font-size: 12px;">Φόρτωση...</div>
  172. </div>
  173. </div>
  174. </div>
  175. <script>
  176. // ==========================================
  177. // LIBRESPEED INTEGRATION
  178. // ==========================================
  179. let s = null;
  180. function updateGauge(percentage, color) {
  181. const gauge = document.getElementById('gauge');
  182. let safePercentage = Math.min(percentage, 100);
  183. gauge.style.background = `conic-gradient(${color} ${safePercentage}%, #0b0c10 0)`;
  184. }
  185. function startTest() {
  186. if (s !== null) return;
  187. s = new Speedtest();
  188. // Παίρνουμε την επιλογή από το Dropdown
  189. const selectedServer = document.getElementById('server-select').value;
  190. let baseUrl = "";
  191. if (selectedServer !== "local") {
  192. baseUrl = selectedServer + "/"; // π.χ. http://nodeserver1.com/
  193. }
  194. // --- ΡΥΘΜΙΣΕΙΣ URL ---
  195. // Αν χρησιμοποιείς το garbage.dat άλλαξε το .php σε .dat στην από κάτω γραμμή
  196. s.setParameter("url_dl", baseUrl + "garbage.dat");
  197. s.setParameter("url_ul", baseUrl + "empty.php");
  198. s.setParameter("url_ping", baseUrl + "empty.php");
  199. s.setParameter("url_getIp", baseUrl + "getIP.php");
  200. // UI Setup
  201. document.getElementById('start-btn').disabled = true;
  202. document.getElementById('start-btn').innerText = "Σε εξελιξη...";
  203. document.getElementById('ping-val').innerText = "--";
  204. document.getElementById('dl-val').innerText = "--";
  205. document.getElementById('ul-val').innerText = "--";
  206. document.getElementById('main-val').innerText = "0";
  207. document.getElementById('speed-unit').innerText = "Mbps";
  208. document.getElementById('status-text').innerText = "Εκκίνηση...";
  209. document.getElementById('server-select').disabled = true;
  210. updateGauge(0, '#1f2833');
  211. // Λήψη δεδομένων
  212. s.onupdate = function(data) {
  213. if (data.pingStatus) {
  214. document.getElementById('ping-val').innerText = data.pingStatus;
  215. }
  216. if (data.testState === 1) {
  217. document.getElementById('status-text').innerText = "Download";
  218. document.getElementById('box-ping').classList.remove('active-dl');
  219. document.getElementById('box-dl').classList.add('active-dl');
  220. if (data.dlStatus && data.dlStatus !== "Fail") {
  221. document.getElementById('dl-val').innerText = data.dlStatus;
  222. document.getElementById('main-val').innerText = data.dlStatus;
  223. updateGauge((data.dlStatus / 1000) * 100, '#66fcf1');
  224. }
  225. }
  226. if (data.testState === 3) {
  227. document.getElementById('status-text').innerText = "Upload";
  228. document.getElementById('box-dl').classList.remove('active-dl');
  229. document.getElementById('box-ul').classList.add('active-ul');
  230. if (data.ulStatus && data.ulStatus !== "Fail") {
  231. document.getElementById('ul-val').innerText = data.ulStatus;
  232. document.getElementById('main-val').innerText = data.ulStatus;
  233. updateGauge((data.ulStatus / 1000) * 100, '#c3073f');
  234. }
  235. }
  236. };
  237. // Τέλος Test
  238. s.onend = function(aborted) {
  239. document.getElementById('status-text').innerText = aborted ? "Ακυρώθηκε" : "Ολοκληρώθηκε";
  240. document.getElementById('main-val').innerText = "✓";
  241. document.getElementById('speed-unit').innerText = "";
  242. document.getElementById('box-ul').classList.remove('active-ul');
  243. updateGauge(0, '#1f2833');
  244. document.getElementById('start-btn').innerText = "Επαναληψη";
  245. document.getElementById('start-btn').disabled = false;
  246. document.getElementById('server-select').disabled = false;
  247. s = null;
  248. };
  249. s.start();
  250. }
  251. // ==========================================
  252. // SERVER HUB INTEGRATION (AJAX)
  253. // ==========================================
  254. async function fetchServers() {
  255. const container = document.getElementById('server-list-container');
  256. const selectDropdown = document.getElementById('server-select');
  257. try {
  258. const response = await fetch('index.php?api=1');
  259. const servers = await response.json();
  260. container.innerHTML = '';
  261. // Κρατάμε πάντα τον Τοπικό server στην κορυφή του Dropdown
  262. selectDropdown.innerHTML = '<option value="local">Αυτός ο Server (Τοπικός)</option>';
  263. if(servers.length === 0) {
  264. container.innerHTML = '<div class="server-item" style="justify-content: center; color: #888; font-size: 12px;">Δεν υπάρχουν servers.</div>';
  265. return;
  266. }
  267. servers.forEach(server => {
  268. // Προσθήκη στη Λίστα του Hub
  269. const div = document.createElement('div');
  270. div.className = 'server-item';
  271. const statusClass = server.status === 'online' ? 'status-online' : 'status-offline';
  272. div.innerHTML = `
  273. <span class="server-url">${server.url}</span>
  274. <span class="status-badge ${statusClass}">${server.status}</span>
  275. `;
  276. container.appendChild(div);
  277. // Προσθήκη στο Dropdown (ΜΟΝΟ αν είναι Online)
  278. if(server.status === 'online') {
  279. const option = document.createElement('option');
  280. option.value = server.url;
  281. option.innerText = server.url;
  282. selectDropdown.appendChild(option);
  283. }
  284. });
  285. } catch (error) {
  286. container.innerHTML = '<div style="color: #e74c3c; font-size: 12px;">Σφάλμα φόρτωσης.</div>';
  287. }
  288. }
  289. async function registerServer() {
  290. const input = document.getElementById('new-server-url');
  291. if (!input.value.trim()) return alert('Παρακαλώ εισάγετε URL');
  292. await fetch('index.php?api=1', {
  293. method: 'POST',
  294. headers: { 'Content-Type': 'application/json' },
  295. body: JSON.stringify({ url: input.value.trim() })
  296. });
  297. input.value = '';
  298. fetchServers();
  299. }
  300. window.onload = fetchServers;
  301. </script>
  302. </body>
  303. </html>