| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- <?php
- // ==========================================
- // 1. BACKEND API (Για το Server Hub)
- // ==========================================
- if (isset($_GET['api'])) {
- header('Content-Type: application/json');
- $file = 'servers.json';
-
- if (!file_exists($file)) {
- file_put_contents($file, json_encode([]));
- }
- $data = json_decode(file_get_contents($file), true);
- // Προσθήκη νέου server
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $input = json_decode(file_get_contents('php://input'), true);
- $url = filter_var($input['url'] ?? '', FILTER_SANITIZE_URL);
-
- if ($url) {
- if (!preg_match("~^(?:f|ht)tps?://~i", $url)) $url = "http://" . $url;
-
- // Αφαίρεση του / στο τέλος αν υπάρχει για ομοιομορφία
- $url = rtrim($url, '/');
- $exists = false;
- foreach ($data as $server) {
- if ($server['url'] === $url) $exists = true;
- }
- if (!$exists) {
- $data[] = ['url' => $url];
- file_put_contents($file, json_encode($data));
- }
- echo json_encode(['success' => true]);
- } else {
- echo json_encode(['success' => false, 'error' => 'Invalid URL']);
- }
- exit;
- }
- // Ανάγνωση και Healthcheck
- $result = [];
- foreach ($data as $server) {
- $url = $server['url'];
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_NOBODY, true);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_TIMEOUT, 3);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
-
- $status = ($httpCode >= 200 && $httpCode < 400) ? 'online' : 'offline';
- $result[] = ['url' => $url, 'status' => $status];
- }
- echo json_encode($result);
- exit;
- }
- ?>
- <!DOCTYPE html>
- <html lang="el">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Modern SpeedTest & Server Hub</title>
- <script src="speedtest.js"></script>
- <style>
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap');
- * { box-sizing: border-box; margin: 0; padding: 0; }
- 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; }
- .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; }
- h1 { color: #66fcf1; font-size: 24px; margin-bottom: 30px; letter-spacing: 1px; text-transform: uppercase; }
-
- /* SpeedTest Metrics */
- .metrics { display: flex; justify-content: space-between; margin-bottom: 40px; gap: 15px; }
- .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; }
- .metric.active-dl { box-shadow: inset 0 0 15px rgba(102, 252, 241, 0.3); border: 1px solid #45a29e; }
- .metric.active-ul { box-shadow: inset 0 0 15px rgba(195, 7, 63, 0.3); border: 1px solid #c3073f; }
- .metric span { display: block; font-size: 11px; text-transform: uppercase; color: #45a29e; letter-spacing: 1px; margin-bottom: 5px; }
- .metric strong { font-size: 22px; color: #ffffff; font-weight: 600; }
- .metric small { font-size: 12px; color: #66fcf1; margin-left: 2px; }
-
- /* Gauge */
- .gauge-container { position: relative; width: 220px; height: 220px; margin: 0 auto 40px; }
- .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; }
- .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); }
- .current-status { font-size: 14px; color: #45a29e; text-transform: uppercase; letter-spacing: 2px; margin-bottom: -5px; height: 20px; }
- .current-speed { font-size: 48px; font-weight: 600; color: #66fcf1; text-shadow: 0 0 10px rgba(102, 252, 241, 0.4); }
- .speed-unit { font-size: 14px; color: #c5c6c7; }
-
- /* Controls (Dropdown & Button) */
- .controls-group { margin-bottom: 30px; }
- 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;}
-
- 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; }
- button:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(102, 252, 241, 0.4); }
- button:disabled { background: #45a29e; opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }
- /* Hub Section */
- hr { border: 0; height: 1px; background: #45a29e; opacity: 0.3; margin: 30px 0; }
- .hub-section h3 { color: #45a29e; font-size: 16px; margin-bottom: 15px; text-transform: uppercase; }
- .input-group { display: flex; gap: 10px; margin-bottom: 20px; }
- .input-group input { flex: 1; padding: 12px; border-radius: 8px; border: 1px solid #0b0c10; background: #0b0c10; color: #fff; outline: none; }
- .input-group button { width: auto; margin: 0; padding: 12px 20px; font-size: 14px; border-radius: 8px; }
- .server-list { text-align: left; background: #0b0c10; padding: 15px; border-radius: 12px; max-height: 200px; overflow-y: auto; }
- .server-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #1f2833; }
- .server-item:last-child { border-bottom: none; }
- .server-url { font-size: 14px; color: #c5c6c7; word-break: break-all; }
- .status-badge { font-size: 12px; padding: 4px 8px; border-radius: 12px; text-transform: uppercase; font-weight: 600; }
- .status-online { background: rgba(46, 204, 113, 0.2); color: #2ecc71; }
- .status-offline { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
- /* Download Agent Section */
- .download-agent-box {
- background: #0b0c10;
- border: 1px dashed #45a29e;
- padding: 20px;
- border-radius: 12px;
- margin-bottom: 25px;
- text-align: center;
- }
- .download-agent-box p {
- font-size: 13px;
- color: #c5c6c7;
- margin-bottom: 15px;
- letter-spacing: 0.5px;
- }
- .download-btn {
- display: inline-block;
- background: transparent;
- color: #66fcf1;
- text-decoration: none;
- padding: 10px 25px;
- border: 1px solid #66fcf1;
- border-radius: 20px;
- font-size: 12px;
- text-transform: uppercase;
- letter-spacing: 1px;
- transition: all 0.3s ease;
- }
- .download-btn:hover {
- background: rgba(102, 252, 241, 0.1);
- box-shadow: 0 0 15px rgba(102, 252, 241, 0.3);
- transform: translateY(-2px);
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>WiSpeed.awmn</h1>
- <div class="metrics">
- <div class="metric" id="box-ping"><span>Ping</span><strong id="ping-val">--</strong><small>ms</small></div>
- <div class="metric" id="box-dl"><span>Download</span><strong id="dl-val">--</strong><small>Mb/s</small></div>
- <div class="metric" id="box-ul"><span>Upload</span><strong id="ul-val">--</strong><small>Mb/s</small></div>
- </div>
- <div class="gauge-container">
- <div class="gauge" id="gauge">
- <div class="gauge-inner">
- <div class="current-status" id="status-text">Ready</div>
- <div class="current-speed" id="main-val">0</div>
- <div class="speed-unit" id="speed-unit">Mbps</div>
- </div>
- </div>
- </div>
- <div class="controls-group">
- <select id="server-select">
- <option value="local">Αυτός ο Server (Τοπικός)</option>
- </select>
- <button id="start-btn" onclick="startTest()">Έναρξη</button>
- </div>
- <hr>
- <div class="hub-section">
- <div class="download-agent-box">
- <p>Host and register your own WiSpeed Server</p>
- <a href="speedagent.tar.gz" class="download-btn">⬇ Download Agent</a>
- </div>
- <h3>Καταχώρηση Νέου Node</h3>
- <div class="input-group">
- <input type="text" id="new-server-url" placeholder="π.χ. nodeserver1.com">
- <button onclick="registerServer()">Προσθήκη</button>
- </div>
- <h3>Ενεργοί Servers (Healthcheck)</h3>
- <div class="server-list" id="server-list-container">
- <div class="server-item" style="justify-content: center; color: #888; font-size: 12px;">Φόρτωση...</div>
- </div>
- </div>
- </div>
- <script>
- // ==========================================
- // LIBRESPEED INTEGRATION
- // ==========================================
- let s = null;
- function updateGauge(percentage, color) {
- const gauge = document.getElementById('gauge');
- let safePercentage = Math.min(percentage, 100);
- gauge.style.background = `conic-gradient(${color} ${safePercentage}%, #0b0c10 0)`;
- }
- function startTest() {
- if (s !== null) return;
- s = new Speedtest();
-
- // Παίρνουμε την επιλογή από το Dropdown
- const selectedServer = document.getElementById('server-select').value;
- let baseUrl = "";
-
- if (selectedServer !== "local") {
- baseUrl = selectedServer + "/"; // π.χ. http://nodeserver1.com/
- }
-
- // --- ΡΥΘΜΙΣΕΙΣ URL ---
- // Αν χρησιμοποιείς το garbage.dat άλλαξε το .php σε .dat στην από κάτω γραμμή
- s.setParameter("url_dl", baseUrl + "garbage.dat");
- s.setParameter("url_ul", baseUrl + "empty.php");
- s.setParameter("url_ping", baseUrl + "empty.php");
- s.setParameter("url_getIp", baseUrl + "getIP.php");
-
- // UI Setup
- document.getElementById('start-btn').disabled = true;
- document.getElementById('start-btn').innerText = "Σε εξελιξη...";
- document.getElementById('ping-val').innerText = "--";
- document.getElementById('dl-val').innerText = "--";
- document.getElementById('ul-val').innerText = "--";
- document.getElementById('main-val').innerText = "0";
- document.getElementById('speed-unit').innerText = "Mbps";
- document.getElementById('status-text').innerText = "Εκκίνηση...";
- document.getElementById('server-select').disabled = true;
- updateGauge(0, '#1f2833');
- // Λήψη δεδομένων
- s.onupdate = function(data) {
- if (data.pingStatus) {
- document.getElementById('ping-val').innerText = data.pingStatus;
- }
- if (data.testState === 1) {
- document.getElementById('status-text').innerText = "Download";
- document.getElementById('box-ping').classList.remove('active-dl');
- document.getElementById('box-dl').classList.add('active-dl');
-
- if (data.dlStatus && data.dlStatus !== "Fail") {
- document.getElementById('dl-val').innerText = data.dlStatus;
- document.getElementById('main-val').innerText = data.dlStatus;
- updateGauge((data.dlStatus / 1000) * 100, '#66fcf1');
- }
- }
- if (data.testState === 3) {
- document.getElementById('status-text').innerText = "Upload";
- document.getElementById('box-dl').classList.remove('active-dl');
- document.getElementById('box-ul').classList.add('active-ul');
-
- if (data.ulStatus && data.ulStatus !== "Fail") {
- document.getElementById('ul-val').innerText = data.ulStatus;
- document.getElementById('main-val').innerText = data.ulStatus;
- updateGauge((data.ulStatus / 1000) * 100, '#c3073f');
- }
- }
- };
- // Τέλος Test
- s.onend = function(aborted) {
- document.getElementById('status-text').innerText = aborted ? "Ακυρώθηκε" : "Ολοκληρώθηκε";
- document.getElementById('main-val').innerText = "✓";
- document.getElementById('speed-unit').innerText = "";
- document.getElementById('box-ul').classList.remove('active-ul');
- updateGauge(0, '#1f2833');
-
- document.getElementById('start-btn').innerText = "Επαναληψη";
- document.getElementById('start-btn').disabled = false;
- document.getElementById('server-select').disabled = false;
- s = null;
- };
- s.start();
- }
- // ==========================================
- // SERVER HUB INTEGRATION (AJAX)
- // ==========================================
- async function fetchServers() {
- const container = document.getElementById('server-list-container');
- const selectDropdown = document.getElementById('server-select');
-
- try {
- const response = await fetch('index.php?api=1');
- const servers = await response.json();
-
- container.innerHTML = '';
- // Κρατάμε πάντα τον Τοπικό server στην κορυφή του Dropdown
- selectDropdown.innerHTML = '<option value="local">Αυτός ο Server (Τοπικός)</option>';
- if(servers.length === 0) {
- container.innerHTML = '<div class="server-item" style="justify-content: center; color: #888; font-size: 12px;">Δεν υπάρχουν servers.</div>';
- return;
- }
- servers.forEach(server => {
- // Προσθήκη στη Λίστα του Hub
- const div = document.createElement('div');
- div.className = 'server-item';
- const statusClass = server.status === 'online' ? 'status-online' : 'status-offline';
- div.innerHTML = `
- <span class="server-url">${server.url}</span>
- <span class="status-badge ${statusClass}">${server.status}</span>
- `;
- container.appendChild(div);
- // Προσθήκη στο Dropdown (ΜΟΝΟ αν είναι Online)
- if(server.status === 'online') {
- const option = document.createElement('option');
- option.value = server.url;
- option.innerText = server.url;
- selectDropdown.appendChild(option);
- }
- });
- } catch (error) {
- container.innerHTML = '<div style="color: #e74c3c; font-size: 12px;">Σφάλμα φόρτωσης.</div>';
- }
- }
- async function registerServer() {
- const input = document.getElementById('new-server-url');
- if (!input.value.trim()) return alert('Παρακαλώ εισάγετε URL');
- await fetch('index.php?api=1', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url: input.value.trim() })
- });
- input.value = '';
- fetchServers();
- }
- window.onload = fetchServers;
- </script>
- </body>
- </html>
|