Spaces:
Running
Running
<?xml version="1.0" encoding="utf-8"?> <manifest package="com.example.wifitesterpro" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- لعرض SSID بدقة على Android 10+ --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application android:allowBackup="true" android:label="Wi-Fi Tester Pro" android:supportsRtl="true" android:theme="@style/Theme.WifiTesterPro" android:usesCleartextTraffic="true"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> // Top-level build file plugins { id 'com.android.application' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.9.24' apply false } pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "WifiTesterPro" include ':app'plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' } android { namespace 'com.example.wifitesterpro' compileSdk 34 defaultConfig { applicationId "com.example.wifitesterpro" minSdk 26 targetSdk 34 versionCode 1 versionName "1.0.0" } buildFeatures { viewBinding true } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = '17' } } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1" implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' } <resources xmlns:tools="http://schemas.android.com/tools"> <style name="Theme.WifiTesterPro" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> </style> </resources> <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:text="اختبار شبكة Wi-Fi (نسخة مطوّرة)" android:textSize="20sp" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <EditText android:id="@+id/etHost" android:hint="الهوست للاختبار (مثال: 8.8.8.8 أو www.example.com)" android:inputType="textUri" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp"/> <EditText android:id="@+id/etPorts" android:hint="منافذ لفحصها (مثال: 53,80,443,8080)" android:inputType="text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"/> <EditText android:id="@+id/etUrl" android:hint="رابط HTTP/HTTPS لاختبار الاستجابة (مثال: https://www.google.com)" android:inputType="textUri" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"/> <Button android:id="@+id/btnRunAll" android:text="تشغيل كل الاختبارات" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp"/> <Button android:id="@+id/btnExport" android:text="تصدير النتائج CSV" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"/> <TextView android:id="@+id/tvInfo" android:text="معلومات الشبكة..." android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp"/> <TextView android:id="@+id/tvResults" android:text="نتائج الاختبارات..." android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp"/> </LinearLayout> </ScrollView> package com.example.wifitesterpro import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.wifi.WifiManager import android.text.format.Formatter import kotlinx.coroutines.* import java.io.BufferedInputStream import java.net.HttpURLConnection import java.net.InetAddress import java.net.InetSocketAddress import java.net.Socket import java.net.URL import java.util.concurrent.TimeUnit import kotlin.math.roundToInt data class WifiInfoData( val ssid: String?, val ip: String?, val gateway: String?, val linkSpeedMbps: Int?, val rssi: Int?, val frequencyMhz: Int? ) data class PingStats( val transmitted: Int, val received: Int, val lossPercent: Double, val minMs: Double?, val avgMs: Double?, val maxMs: Double? ) object NetworkTester { fun getWifiInfo(context: Context): WifiInfoData { val wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val conn = wifi.connectionInfo val dhcp = wifi.dhcpInfo val ip = dhcp?.let { Formatter.formatIpAddress(it.ipAddress) } val gateway = dhcp?.let { Formatter.formatIpAddress(it.gateway) } return WifiInfoData( ssid = conn?.ssid?.replace("\"",""), ip = ip, gateway = gateway, linkSpeedMbps = conn?.linkSpeed, rssi = conn?.rssi, frequencyMhz = conn?.frequency ) } fun isConnectedToInternet(context: Context): Boolean { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val nw = cm.activeNetwork ?: return false val cap = cm.getNetworkCapabilities(nw) ?: return false return cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } suspend fun pingRaw(host: String, count: Int = 4, timeoutSec: Int = 4): String = withContext(Dispatchers.IO) { try { val proc = ProcessBuilder() .command("ping", "-c", count.toString(), "-W", timeoutSec.toString(), host) .redirectErrorStream(true) .start() proc.waitFor((timeoutSec * (count + 1)).toLong(), TimeUnit.SECONDS) proc.inputStream.bufferedReader().use { it.readText() } } catch (e: Exception) { "Ping error: ${e.message}" } } fun parsePingStats(output: String, fallbackCount: Int): PingStats { // يحاول استخراج الإحصائيات من مخرجات ping القياسية val tx = Regex("(\\d+) packets transmitted").find(output)?.groupValues?.get(1)?.toIntOrNull() ?: fallbackCount val rx = Regex("(\\d+) received").find(output)?.groupValues?.get(1)?.toIntOrNull() ?: 0 val loss = Regex("(\\d+(?:\\.\\d+)?)% packet loss").find(output)?.groupValues?.get(1)?.toDoubleOrNull() ?: run { if (tx > 0) ((tx - rx).toDouble() / tx) * 100.0 else 100.0 } val rtt = Regex("min/avg/max/(?:mdev|stddev) = ([\\d.]+)/([\\d.]+)/([\\d.]+)/").find(output) val minMs = rtt?.groupValues?.get(1)?.toDoubleOrNull() val avgMs = rtt?.groupValues?.get(2)?.toDoubleOrNull() val maxMs = rtt?.groupValues?.get(3)?.toDoubleOrNull() return PingStats(tx, rx, loss, minMs, avgMs, maxMs) } suspend fun tcpConnect(host: String, port: Int, timeoutMs: Int = 3000): Pair<Boolean, Long> = withContext(Dispatchers.IO) { val start = System.nanoTime() return@withContext try { Socket().use { s -> s.connect(InetSocketAddress(host, port), timeoutMs) } val ms = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) true to ms } catch (_: Exception) { val ms = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) false to ms } } suspend fun scanPorts(host: String, ports: List<Int>, timeoutMs: Int = 1000): List<String> = coroutineScope { ports.map { p -> async(Dispatchers.IO) { val (ok, ms) = tcpConnect(host, p, timeoutMs) "${host}:${p} -> ${if (ok) "OPEN" else "CLOSED"} (${ms}ms)" } }.awaitAll() } suspend fun httpGet(urlStr: String, timeoutMs: Int = 6000): String = withContext(Dispatchers.IO) { try { val url = URL(urlStr) val conn = (url.openConnection() as HttpURLConnection).apply { connectTimeout = timeoutMs readTimeout = timeoutMs requestMethod = "GET" } val start = System.nanoTime() val code = conn.responseCode val latency = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) conn.inputStream?.close() "HTTP GET $urlStr → $code (${latency}ms)" } catch (e: Exception) { "HTTP GET $urlStr: error ${e.message}" } } suspend fun captivePortalCheck(): String = httpGet("http://clients3.google.com/generate_204", 5000) suspend fun downloadSpeedKbps( testUrl: String = "https://speed.hetzner.de/100MB.bin", bytesToRead: Long = 2_000_000 ): String = withContext(Dispatchers.IO) { try { val url = URL(testUrl) val conn = (url.openConnection() as HttpURLConnection).apply { connectTimeout = 5000 readTimeout = 8000 requestMethod = "GET" } var readTotal = 0L val start = System.nanoTime() BufferedInputStream(conn.inputStream).use { input -> val buf = ByteArray(64 * 1024) var n: Int while (readTotal < bytesToRead && input.read(buf).also { n = it } != -1) { readTotal += n } } val elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start).coerceAtLeast(1) val kbps = (readTotal / 1024.0) / (elapsedMs / 1000.0) "Estimated download: %.1f KB/s (%.2f MB in %d ms)".format(kbps, readTotal/1_000_000.0, elapsedMs) } catch (e: Exception) { "Download test error: ${e.message}" } } suspend fun dnsLookup(host: String): String = withContext(Dispatchers.IO) { try { val addr = InetAddress.getByName(host) "DNS $host → ${addr.hostAddress}" } catch (e: Exception) { "DNS $host: error ${e.message}" } } } package com.example.wifitesterpro import android.content.ContentValues import android.content.Context import android.os.Build import android.provider.MediaStore import java.io.OutputStream import java.nio.charset.Charset import java.text.SimpleDateFormat import java.util.* object CsvExporter { fun saveToDownloads(context: Context, filePrefix: String, csvContent: String): String { val ts = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) val filename = "${filePrefix}_${ts}.csv" val resolver = context.contentResolver val collection = if (Build.VERSION.SDK_INT >= 29) { MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) } else { MediaStore.Files.getContentUri("external") } val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, filename) put(MediaStore.MediaColumns.MIME_TYPE, "text/csv") } val uri = resolver.insert(collection, values) ?: throw IllegalStateException("Failed to create file in Downloads") resolver.openOutputStream(uri)?.use { out: OutputStream -> out.write(csvContent.toByteArray(Charset.forName("UTF-8"))) out.flush() } ?: throw IllegalStateException("Failed to open output stream") return filename } } package com.example.wifitesterpro import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.example.wifitesterpro.databinding.ActivityMainBinding import kotlinx.coroutines.* class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val requestLocationPermission = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { granted -> if (!granted) { Toast.makeText(this, "لإظهار SSID بدقة على Android 10+، فعّل إذن الموقع.", Toast.LENGTH_LONG).show() } showWifiInfo() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.btnRunAll.setOnClickListener { runAllTests() } binding.btnExport.setOnClickListener { exportCsv() } maybeAskLocation() showWifiInfo() } private fun maybeAskLocation() { val perm = Manifest.permission.ACCESS_FINE_LOCATION if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) { requestLocationPermission.launch(perm) } } private fun showWifiInfo() { val info = NetworkTester.getWifiInfo(this) val connected = NetworkTester.isConnectedToInternet(this) val text = buildString { appendLine("الحالة: ${if (connected) "متصل بالإنترنت" else "غير متصل"}") appendLine("SSID: ${info.ssid ?: "غير متاح"}") appendLine("IP: ${info.ip ?: "غير متاح"}") appendLine("Gateway: ${info.gateway ?: "غير متاح"}") appendLine("Link Speed: ${info.linkSpeedMbps ?: "?"} Mbps") appendLine("RSSI: ${info.rssi ?: "?"} dBm") appendLine("Frequency: ${info.frequencyMhz ?: "?"} MHz") } binding.tvInfo.text = text } private fun runAllTests() { val defaultHost = NetworkTester.getWifiInfo(this).gateway ?: "8.8.8.8" val host = binding.etHost.text.toString().ifBlank { defaultHost } val ports = binding.etPorts.text.toString().ifBlank { "53,80,443" } .split(",").mapNotNull { it.trim().toIntOrNull() }.distinct() val url = binding.etUrl.text.toString().ifBlank { "https://www.google.com/generate_204" } binding.tvResults.text = "جاري التنفيذ..." scope.launch { val lines = mutableListOf<String>() lines += "=== General ===" lines += NetworkTester.captivePortalCheck() lines += "\n=== DNS ===" lines += NetworkTester.dnsLookup(host) lines += "\n=== Ping ===" val raw = NetworkTester.pingRaw(host, count = 5, timeoutSec = 3) val stats = NetworkTester.parsePingStats(raw, 5) lines += "Ping $host → tx=${stats.transmitted}, rx=${stats.received}, loss=${"%.1f".format(stats.lossPercent)}%" lines += "rtt min/avg/max = ${stats.minMs ?: "-"} / ${stats.avgMs ?: "-"} / ${stats.maxMs ?: "-"} ms" lines += "\n=== TCP Connect ===" val (ok443, ms443) = NetworkTester.tcpConnect(host, 443, 3000) lines += "TCP $host:443 → ${if (ok443) "OPEN" else "CLOSED"} (${ms443}ms)" if (ports.isNotEmpty()) { lines += "\n=== Port Scan (${host}) ===" NetworkTester.scanPorts(host, ports, 1200).forEach { lines += it } } lines += "\n=== HTTP Test ===" lines += NetworkTester.httpGet(url, 6000) lines += "\n=== Download Speed (est.) ===" lines += NetworkTester.downloadSpeedKbps() binding.tvResults.text = lines.joinToString("\n") } } private fun exportCsv() { val infoText = binding.tvInfo.text.toString() val resultsText = binding.tvResults.text.toString() if (resultsText.isBlank()) { Toast.makeText(this, "لا توجد نتائج لتصديرها.", Toast.LENGTH_SHORT).show() return } val csv = buildString { appendLine("Section,Key,Value") infoText.lines().forEach { line -> val parts = line.split(":", limit = 2) if (parts.size == 2) { appendLine("Info,${parts[0].trim()},${parts[1].trim()}") } } appendLine("Results,Raw,\"\"\"${resultsText.replace("\"","\"\"")}\"\"\"") } try { val name = CsvExporter.saveToDownloads(this, "wifi_tester_results", csv) Toast.makeText(this, "تم الحفظ في التنزيلات: $name", Toast.LENGTH_LONG).show() } catch (e: Exception) { Toast.makeText(this, "فشل الحفظ: ${e.message}", Toast.LENGTH_LONG).show() } } override fun onDestroy() { super.onDestroy() scope.cancel() } } - Initial Deployment
Browse files- README.md +7 -5
- index.html +554 -19
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: sorry-1234
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,554 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Wi-Fi Tester Pro</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<style>
|
| 10 |
+
.fade-in {
|
| 11 |
+
animation: fadeIn 0.3s ease-in-out;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
@keyframes fadeIn {
|
| 15 |
+
from { opacity: 0; }
|
| 16 |
+
to { opacity: 1; }
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.wifi-signal {
|
| 20 |
+
position: relative;
|
| 21 |
+
width: 24px;
|
| 22 |
+
height: 24px;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.wifi-signal .bar {
|
| 26 |
+
position: absolute;
|
| 27 |
+
bottom: 0;
|
| 28 |
+
left: 50%;
|
| 29 |
+
transform: translateX(-50%);
|
| 30 |
+
border: 2px solid currentColor;
|
| 31 |
+
border-radius: 50%;
|
| 32 |
+
border-top-color: transparent;
|
| 33 |
+
border-left-color: transparent;
|
| 34 |
+
border-right-color: transparent;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.wifi-signal .bar-1 {
|
| 38 |
+
width: 6px;
|
| 39 |
+
height: 6px;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.wifi-signal .bar-2 {
|
| 43 |
+
width: 12px;
|
| 44 |
+
height: 12px;
|
| 45 |
+
animation-delay: 0.2s;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.wifi-signal .bar-3 {
|
| 49 |
+
width: 18px;
|
| 50 |
+
height: 18px;
|
| 51 |
+
animation-delay: 0.4s;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.wifi-signal .bar-4 {
|
| 55 |
+
width: 24px;
|
| 56 |
+
height: 24px;
|
| 57 |
+
animation-delay: 0.6s;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.pulse {
|
| 61 |
+
animation: pulse 2s infinite;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
@keyframes pulse {
|
| 65 |
+
0% { opacity: 0.6; }
|
| 66 |
+
50% { opacity: 1; }
|
| 67 |
+
100% { opacity: 0.6; }
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.progress-bar {
|
| 71 |
+
height: 4px;
|
| 72 |
+
background-color: #e5e7eb;
|
| 73 |
+
border-radius: 2px;
|
| 74 |
+
overflow: hidden;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.progress-bar-fill {
|
| 78 |
+
height: 100%;
|
| 79 |
+
background-color: #3b82f6;
|
| 80 |
+
transition: width 0.3s ease;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.result-card {
|
| 84 |
+
transition: all 0.3s ease;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.result-card:hover {
|
| 88 |
+
transform: translateY(-2px);
|
| 89 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
</head>
|
| 93 |
+
<body class="bg-gray-50 text-gray-800 font-sans">
|
| 94 |
+
<div class="min-h-screen flex flex-col">
|
| 95 |
+
<!-- Header -->
|
| 96 |
+
<header class="bg-blue-600 text-white shadow-md">
|
| 97 |
+
<div class="container mx-auto px-4 py-4 flex items-center justify-between">
|
| 98 |
+
<div class="flex items-center space-x-3">
|
| 99 |
+
<div class="wifi-signal text-white">
|
| 100 |
+
<div class="bar bar-1"></div>
|
| 101 |
+
<div class="bar bar-2"></div>
|
| 102 |
+
<div class="bar bar-3"></div>
|
| 103 |
+
<div class="bar bar-4"></div>
|
| 104 |
+
</div>
|
| 105 |
+
<h1 class="text-xl font-bold">Wi-Fi Tester Pro</h1>
|
| 106 |
+
</div>
|
| 107 |
+
<button id="darkModeToggle" class="p-2 rounded-full hover:bg-blue-700 transition">
|
| 108 |
+
<i class="fas fa-moon"></i>
|
| 109 |
+
</button>
|
| 110 |
+
</div>
|
| 111 |
+
</header>
|
| 112 |
+
|
| 113 |
+
<!-- Main Content -->
|
| 114 |
+
<main class="flex-grow container mx-auto px-4 py-6">
|
| 115 |
+
<div class="max-w-4xl mx-auto">
|
| 116 |
+
<!-- Network Info Card -->
|
| 117 |
+
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-6 fade-in">
|
| 118 |
+
<div class="bg-blue-600 px-4 py-3 flex items-center justify-between">
|
| 119 |
+
<h2 class="text-white font-semibold flex items-center">
|
| 120 |
+
<i class="fas fa-network-wired mr-2"></i> Network Information
|
| 121 |
+
</h2>
|
| 122 |
+
<button id="refreshNetworkInfo" class="text-white hover:text-blue-200 transition">
|
| 123 |
+
<i class="fas fa-sync-alt"></i>
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
<div class="p-4">
|
| 127 |
+
<div id="networkInfo" class="space-y-2 text-sm">
|
| 128 |
+
<div class="flex justify-between">
|
| 129 |
+
<span class="text-gray-500">Status:</span>
|
| 130 |
+
<span class="font-medium text-green-600">Connected to Internet</span>
|
| 131 |
+
</div>
|
| 132 |
+
<div class="flex justify-between">
|
| 133 |
+
<span class="text-gray-500">SSID:</span>
|
| 134 |
+
<span class="font-medium">MyWiFiNetwork</span>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="flex justify-between">
|
| 137 |
+
<span class="text-gray-500">IP Address:</span>
|
| 138 |
+
<span class="font-medium">192.168.1.15</span>
|
| 139 |
+
</div>
|
| 140 |
+
<div class="flex justify-between">
|
| 141 |
+
<span class="text-gray-500">Gateway:</span>
|
| 142 |
+
<span class="font-medium">192.168.1.1</span>
|
| 143 |
+
</div>
|
| 144 |
+
<div class="flex justify-between">
|
| 145 |
+
<span class="text-gray-500">Link Speed:</span>
|
| 146 |
+
<span class="font-medium">144 Mbps</span>
|
| 147 |
+
</div>
|
| 148 |
+
<div class="flex justify-between">
|
| 149 |
+
<span class="text-gray-500">Signal Strength:</span>
|
| 150 |
+
<div class="flex items-center">
|
| 151 |
+
<span class="font-medium mr-2">-56 dBm</span>
|
| 152 |
+
<div class="flex space-x-1">
|
| 153 |
+
<div class="w-1 h-3 bg-green-500 rounded-full"></div>
|
| 154 |
+
<div class="w-1 h-4 bg-green-500 rounded-full"></div>
|
| 155 |
+
<div class="w-1 h-5 bg-green-500 rounded-full"></div>
|
| 156 |
+
<div class="w-1 h-6 bg-green-500 rounded-full"></div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
<div class="flex justify-between">
|
| 161 |
+
<span class="text-gray-500">Frequency:</span>
|
| 162 |
+
<span class="font-medium">2412 MHz (2.4 GHz)</span>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
|
| 168 |
+
<!-- Test Configuration -->
|
| 169 |
+
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-6 fade-in">
|
| 170 |
+
<div class="bg-blue-600 px-4 py-3">
|
| 171 |
+
<h2 class="text-white font-semibold flex items-center">
|
| 172 |
+
<i class="fas fa-cog mr-2"></i> Test Configuration
|
| 173 |
+
</h2>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="p-4 space-y-4">
|
| 176 |
+
<div>
|
| 177 |
+
<label for="hostInput" class="block text-sm font-medium text-gray-700 mb-1">
|
| 178 |
+
<i class="fas fa-server mr-1"></i> Host to Test
|
| 179 |
+
</label>
|
| 180 |
+
<div class="relative">
|
| 181 |
+
<input type="text" id="hostInput" placeholder="e.g., 8.8.8.8 or www.example.com"
|
| 182 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
| 183 |
+
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
| 184 |
+
<i class="fas fa-question-circle text-gray-400 hover:text-blue-500 cursor-pointer"
|
| 185 |
+
title="The host or IP address to test connectivity against"></i>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
<div>
|
| 191 |
+
<label for="portsInput" class="block text-sm font-medium text-gray-700 mb-1">
|
| 192 |
+
<i class="fas fa-plug mr-1"></i> Ports to Scan
|
| 193 |
+
</label>
|
| 194 |
+
<div class="relative">
|
| 195 |
+
<input type="text" id="portsInput" placeholder="e.g., 53,80,443,8080"
|
| 196 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
| 197 |
+
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
| 198 |
+
<i class="fas fa-question-circle text-gray-400 hover:text-blue-500 cursor-pointer"
|
| 199 |
+
title="Comma-separated list of ports to scan (e.g., 53,80,443)"></i>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<div>
|
| 205 |
+
<label for="urlInput" class="block text-sm font-medium text-gray-700 mb-1">
|
| 206 |
+
<i class="fas fa-globe mr-1"></i> URL to Test
|
| 207 |
+
</label>
|
| 208 |
+
<div class="relative">
|
| 209 |
+
<input type="text" id="urlInput" placeholder="e.g., https://www.google.com"
|
| 210 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
| 211 |
+
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
| 212 |
+
<i class="fas fa-question-circle text-gray-400 hover:text-blue-500 cursor-pointer"
|
| 213 |
+
title="URL to test HTTP/HTTPS connectivity"></i>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div class="flex flex-col sm:flex-row gap-3 pt-2">
|
| 219 |
+
<button id="runAllTestsBtn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition flex items-center justify-center">
|
| 220 |
+
<i class="fas fa-play-circle mr-2"></i> Run All Tests
|
| 221 |
+
</button>
|
| 222 |
+
<button id="exportResultsBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-md transition flex items-center justify-center">
|
| 223 |
+
<i class="fas fa-file-export mr-2"></i> Export Results
|
| 224 |
+
</button>
|
| 225 |
+
</div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
<!-- Test Results -->
|
| 230 |
+
<div class="bg-white rounded-lg shadow-md overflow-hidden fade-in">
|
| 231 |
+
<div class="bg-blue-600 px-4 py-3 flex items-center justify-between">
|
| 232 |
+
<h2 class="text-white font-semibold flex items-center">
|
| 233 |
+
<i class="fas fa-chart-bar mr-2"></i> Test Results
|
| 234 |
+
</h2>
|
| 235 |
+
<div id="testStatus" class="text-sm text-blue-100 flex items-center">
|
| 236 |
+
<span>Ready</span>
|
| 237 |
+
</div>
|
| 238 |
+
</div>
|
| 239 |
+
<div class="p-4">
|
| 240 |
+
<div id="progressContainer" class="mb-4 hidden">
|
| 241 |
+
<div class="flex justify-between text-sm text-gray-600 mb-1">
|
| 242 |
+
<span>Running tests...</span>
|
| 243 |
+
<span id="progressPercent">0%</span>
|
| 244 |
+
</div>
|
| 245 |
+
<div class="progress-bar">
|
| 246 |
+
<div id="progressBarFill" class="progress-bar-fill" style="width: 0%"></div>
|
| 247 |
+
</div>
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<div id="testResults" class="space-y-4">
|
| 251 |
+
<div class="text-center text-gray-500 py-8">
|
| 252 |
+
<i class="fas fa-wifi text-4xl mb-3 text-gray-300"></i>
|
| 253 |
+
<p>No test results yet. Click "Run All Tests" to begin.</p>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
</main>
|
| 260 |
+
|
| 261 |
+
<!-- Footer -->
|
| 262 |
+
<footer class="bg-gray-100 border-t border-gray-200 py-4">
|
| 263 |
+
<div class="container mx-auto px-4 text-center text-gray-600 text-sm">
|
| 264 |
+
<p>Wi-Fi Tester Pro © 2023 | Version 1.0.0</p>
|
| 265 |
+
</div>
|
| 266 |
+
</footer>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
<!-- Toast Notification -->
|
| 270 |
+
<div id="toast" class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-md shadow-lg transform translate-y-10 opacity-0 transition-all duration-300 hidden">
|
| 271 |
+
<div class="flex items-center">
|
| 272 |
+
<i id="toastIcon" class="fas fa-info-circle mr-2"></i>
|
| 273 |
+
<span id="toastMessage">This is a toast message</span>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
<script>
|
| 278 |
+
// Dark mode toggle
|
| 279 |
+
const darkModeToggle = document.getElementById('darkModeToggle');
|
| 280 |
+
darkModeToggle.addEventListener('click', () => {
|
| 281 |
+
document.documentElement.classList.toggle('dark');
|
| 282 |
+
const icon = darkModeToggle.querySelector('i');
|
| 283 |
+
if (document.documentElement.classList.contains('dark')) {
|
| 284 |
+
icon.classList.replace('fa-moon', 'fa-sun');
|
| 285 |
+
document.body.classList.remove('bg-gray-50');
|
| 286 |
+
document.body.classList.add('bg-gray-900', 'text-gray-100');
|
| 287 |
+
showToast('Dark mode enabled', 'success');
|
| 288 |
+
} else {
|
| 289 |
+
icon.classList.replace('fa-sun', 'fa-moon');
|
| 290 |
+
document.body.classList.remove('bg-gray-900', 'text-gray-100');
|
| 291 |
+
document.body.classList.add('bg-gray-50', 'text-gray-800');
|
| 292 |
+
showToast('Light mode enabled', 'success');
|
| 293 |
+
}
|
| 294 |
+
});
|
| 295 |
+
|
| 296 |
+
// Toast notification function
|
| 297 |
+
function showToast(message, type = 'info') {
|
| 298 |
+
const toast = document.getElementById('toast');
|
| 299 |
+
const toastMessage = document.getElementById('toastMessage');
|
| 300 |
+
const toastIcon = document.getElementById('toastIcon');
|
| 301 |
+
|
| 302 |
+
toastMessage.textContent = message;
|
| 303 |
+
|
| 304 |
+
// Set icon and color based on type
|
| 305 |
+
if (type === 'success') {
|
| 306 |
+
toast.classList.remove('bg-gray-800');
|
| 307 |
+
toast.classList.add('bg-green-600');
|
| 308 |
+
toastIcon.classList.replace('fa-info-circle', 'fa-check-circle');
|
| 309 |
+
} else if (type === 'error') {
|
| 310 |
+
toast.classList.remove('bg-gray-800');
|
| 311 |
+
toast.classList.add('bg-red-600');
|
| 312 |
+
toastIcon.classList.replace('fa-info-circle', 'fa-exclamation-circle');
|
| 313 |
+
} else {
|
| 314 |
+
toast.classList.remove('bg-green-600', 'bg-red-600');
|
| 315 |
+
toast.classList.add('bg-gray-800');
|
| 316 |
+
toastIcon.classList.replace('fa-check-circle', 'fa-info-circle');
|
| 317 |
+
toastIcon.classList.replace('fa-exclamation-circle', 'fa-info-circle');
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
// Show toast
|
| 321 |
+
toast.classList.remove('hidden');
|
| 322 |
+
setTimeout(() => {
|
| 323 |
+
toast.classList.remove('translate-y-10', 'opacity-0');
|
| 324 |
+
toast.classList.add('translate-y-0', 'opacity-100');
|
| 325 |
+
}, 10);
|
| 326 |
+
|
| 327 |
+
// Hide after 3 seconds
|
| 328 |
+
setTimeout(() => {
|
| 329 |
+
toast.classList.remove('translate-y-0', 'opacity-100');
|
| 330 |
+
toast.classList.add('translate-y-10', 'opacity-0');
|
| 331 |
+
setTimeout(() => toast.classList.add('hidden'), 300);
|
| 332 |
+
}, 3000);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
// Mock network info refresh
|
| 336 |
+
document.getElementById('refreshNetworkInfo').addEventListener('click', function() {
|
| 337 |
+
const networkInfo = document.getElementById('networkInfo');
|
| 338 |
+
const refreshBtn = this;
|
| 339 |
+
|
| 340 |
+
refreshBtn.classList.add('animate-spin');
|
| 341 |
+
|
| 342 |
+
// Simulate network refresh
|
| 343 |
+
setTimeout(() => {
|
| 344 |
+
// This would be replaced with actual network info in a real app
|
| 345 |
+
const status = Math.random() > 0.1 ? 'Connected to Internet' : 'No Internet Connection';
|
| 346 |
+
const statusColor = status.includes('Connected') ? 'text-green-600' : 'text-red-600';
|
| 347 |
+
|
| 348 |
+
networkInfo.innerHTML = `
|
| 349 |
+
<div class="flex justify-between">
|
| 350 |
+
<span class="text-gray-500">Status:</span>
|
| 351 |
+
<span class="font-medium ${statusColor}">${status}</span>
|
| 352 |
+
</div>
|
| 353 |
+
<div class="flex justify-between">
|
| 354 |
+
<span class="text-gray-500">SSID:</span>
|
| 355 |
+
<span class="font-medium">MyWiFiNetwork</span>
|
| 356 |
+
</div>
|
| 357 |
+
<div class="flex justify-between">
|
| 358 |
+
<span class="text-gray-500">IP Address:</span>
|
| 359 |
+
<span class="font-medium">192.168.1.${Math.floor(Math.random() * 50) + 1}</span>
|
| 360 |
+
</div>
|
| 361 |
+
<div class="flex justify-between">
|
| 362 |
+
<span class="text-gray-500">Gateway:</span>
|
| 363 |
+
<span class="font-medium">192.168.1.1</span>
|
| 364 |
+
</div>
|
| 365 |
+
<div class="flex justify-between">
|
| 366 |
+
<span class="text-gray-500">Link Speed:</span>
|
| 367 |
+
<span class="font-medium">${Math.floor(Math.random() * 200) + 50} Mbps</span>
|
| 368 |
+
</div>
|
| 369 |
+
<div class="flex justify-between">
|
| 370 |
+
<span class="text-gray-500">Signal Strength:</span>
|
| 371 |
+
<div class="flex items-center">
|
| 372 |
+
<span class="font-medium mr-2">-${Math.floor(Math.random() * 40) + 40} dBm</span>
|
| 373 |
+
<div class="flex space-x-1">
|
| 374 |
+
<div class="w-1 h-3 bg-green-500 rounded-full"></div>
|
| 375 |
+
<div class="w-1 h-4 bg-green-500 rounded-full"></div>
|
| 376 |
+
<div class="w-1 h-5 bg-green-500 rounded-full"></div>
|
| 377 |
+
<div class="w-1 h-6 bg-green-500 rounded-full"></div>
|
| 378 |
+
</div>
|
| 379 |
+
</div>
|
| 380 |
+
</div>
|
| 381 |
+
<div class="flex justify-between">
|
| 382 |
+
<span class="text-gray-500">Frequency:</span>
|
| 383 |
+
<span class="font-medium">${Math.random() > 0.5 ? '2412 MHz (2.4 GHz)' : '5180 MHz (5 GHz)'}</span>
|
| 384 |
+
</div>
|
| 385 |
+
`;
|
| 386 |
+
|
| 387 |
+
refreshBtn.classList.remove('animate-spin');
|
| 388 |
+
showToast('Network info refreshed', 'success');
|
| 389 |
+
}, 1000);
|
| 390 |
+
});
|
| 391 |
+
|
| 392 |
+
// Run all tests
|
| 393 |
+
document.getElementById('runAllTestsBtn').addEventListener('click', function() {
|
| 394 |
+
const hostInput = document.getElementById('hostInput').value.trim() || '8.8.8.8';
|
| 395 |
+
const portsInput = document.getElementById('portsInput').value.trim() || '53,80,443';
|
| 396 |
+
const urlInput = document.getElementById('urlInput').value.trim() || 'https://www.google.com';
|
| 397 |
+
|
| 398 |
+
const testResults = document.getElementById('testResults');
|
| 399 |
+
const progressContainer = document.getElementById('progressContainer');
|
| 400 |
+
const progressBarFill = document.getElementById('progressBarFill');
|
| 401 |
+
const progressPercent = document.getElementById('progressPercent');
|
| 402 |
+
const testStatus = document.getElementById('testStatus');
|
| 403 |
+
|
| 404 |
+
// Reset UI
|
| 405 |
+
testResults.innerHTML = '';
|
| 406 |
+
progressContainer.classList.remove('hidden');
|
| 407 |
+
progressBarFill.style.width = '0%';
|
| 408 |
+
progressPercent.textContent = '0%';
|
| 409 |
+
testStatus.innerHTML = '<span class="pulse">Running tests...</span>';
|
| 410 |
+
|
| 411 |
+
// Disable buttons during test
|
| 412 |
+
this.disabled = true;
|
| 413 |
+
document.getElementById('exportResultsBtn').disabled = true;
|
| 414 |
+
|
| 415 |
+
// Simulate running tests with progress updates
|
| 416 |
+
const testPhases = [
|
| 417 |
+
{ name: 'Captive Portal Check', duration: 800 },
|
| 418 |
+
{ name: 'DNS Lookup', duration: 1000 },
|
| 419 |
+
{ name: 'Ping Test', duration: 1500 },
|
| 420 |
+
{ name: 'TCP Connect', duration: 1200 },
|
| 421 |
+
{ name: 'Port Scan', duration: 2000 },
|
| 422 |
+
{ name: 'HTTP Test', duration: 1800 },
|
| 423 |
+
{ name: 'Download Speed', duration: 2500 }
|
| 424 |
+
];
|
| 425 |
+
|
| 426 |
+
let currentProgress = 0;
|
| 427 |
+
const totalDuration = testPhases.reduce((sum, phase) => sum + phase.duration, 0);
|
| 428 |
+
|
| 429 |
+
testPhases.forEach((phase, index) => {
|
| 430 |
+
setTimeout(() => {
|
| 431 |
+
// Update progress
|
| 432 |
+
currentProgress += phase.duration;
|
| 433 |
+
const percent = Math.min(100, Math.round((currentProgress / totalDuration) * 100));
|
| 434 |
+
progressBarFill.style.width = `${percent}%`;
|
| 435 |
+
progressPercent.textContent = `${percent}%`;
|
| 436 |
+
|
| 437 |
+
// Add test result card
|
| 438 |
+
const resultCard = document.createElement('div');
|
| 439 |
+
resultCard.className = 'result-card bg-gray-50 border border-gray-200 rounded-md p-4';
|
| 440 |
+
|
| 441 |
+
// Simulate different test outcomes
|
| 442 |
+
const isSuccess = Math.random() > 0.2;
|
| 443 |
+
const icon = isSuccess ? 'fa-check-circle text-green-500' : 'fa-times-circle text-red-500';
|
| 444 |
+
const status = isSuccess ? 'Success' : 'Failed';
|
| 445 |
+
|
| 446 |
+
resultCard.innerHTML = `
|
| 447 |
+
<div class="flex justify-between items-start mb-2">
|
| 448 |
+
<h3 class="font-medium">${phase.name}</h3>
|
| 449 |
+
<div class="flex items-center">
|
| 450 |
+
<span class="text-sm ${isSuccess ? 'text-green-600' : 'text-red-600'} mr-2">${status}</span>
|
| 451 |
+
<i class="fas ${icon}"></i>
|
| 452 |
+
</div>
|
| 453 |
+
</div>
|
| 454 |
+
<div class="text-sm text-gray-600">
|
| 455 |
+
${getTestDetails(phase.name, isSuccess, hostInput, portsInput, urlInput)}
|
| 456 |
+
</div>
|
| 457 |
+
`;
|
| 458 |
+
|
| 459 |
+
testResults.appendChild(resultCard);
|
| 460 |
+
|
| 461 |
+
// Scroll to bottom
|
| 462 |
+
testResults.scrollTop = testResults.scrollHeight;
|
| 463 |
+
|
| 464 |
+
// If last phase, complete the test
|
| 465 |
+
if (index === testPhases.length - 1) {
|
| 466 |
+
setTimeout(() => {
|
| 467 |
+
progressBarFill.style.width = '100%';
|
| 468 |
+
progressPercent.textContent = '100%';
|
| 469 |
+
testStatus.innerHTML = '<span class="text-green-100">Tests completed</span>';
|
| 470 |
+
|
| 471 |
+
// Re-enable buttons
|
| 472 |
+
document.getElementById('runAllTestsBtn').disabled = false;
|
| 473 |
+
document.getElementById('exportResultsBtn').disabled = false;
|
| 474 |
+
|
| 475 |
+
showToast('All tests completed successfully', 'success');
|
| 476 |
+
}, 300);
|
| 477 |
+
}
|
| 478 |
+
}, testPhases.slice(0, index).reduce((sum, p) => sum + p.duration, 0));
|
| 479 |
+
});
|
| 480 |
+
});
|
| 481 |
+
|
| 482 |
+
// Helper function to generate test details
|
| 483 |
+
function getTestDetails(testName, isSuccess, host, ports, url) {
|
| 484 |
+
switch(testName) {
|
| 485 |
+
case 'Captive Portal Check':
|
| 486 |
+
return isSuccess ?
|
| 487 |
+
'No captive portal detected. Internet access available.' :
|
| 488 |
+
'Potential captive portal detected. Redirect may be required.';
|
| 489 |
+
|
| 490 |
+
case 'DNS Lookup':
|
| 491 |
+
return isSuccess ?
|
| 492 |
+
`DNS resolution for ${host} successful → ${generateRandomIP()}` :
|
| 493 |
+
`Failed to resolve ${host}. DNS server may be unavailable.`;
|
| 494 |
+
|
| 495 |
+
case 'Ping Test':
|
| 496 |
+
if (isSuccess) {
|
| 497 |
+
const min = (Math.random() * 10).toFixed(1);
|
| 498 |
+
const avg = (Math.random() * 20 + 10).toFixed(1);
|
| 499 |
+
const max = (Math.random() * 30 + 20).toFixed(1);
|
| 500 |
+
return `Ping statistics for ${host}: Packets: Sent=4, Received=4, Lost=0 (0% loss)<br>
|
| 501 |
+
Approximate round trip times: min=${min}ms, avg=${avg}ms, max=${max}ms`;
|
| 502 |
+
} else {
|
| 503 |
+
return `Ping request to ${host} failed. Host may be down or blocking ICMP.`;
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
case 'TCP Connect':
|
| 507 |
+
return isSuccess ?
|
| 508 |
+
`Successfully connected to ${host}:443 in ${(Math.random() * 100 + 50).toFixed(0)}ms` :
|
| 509 |
+
`Failed to connect to ${host}:443. Port may be closed or blocked.`;
|
| 510 |
+
|
| 511 |
+
case 'Port Scan':
|
| 512 |
+
const portList = ports.split(',').map(p => p.trim());
|
| 513 |
+
let results = [];
|
| 514 |
+
portList.forEach(port => {
|
| 515 |
+
const portSuccess = Math.random() > 0.3;
|
| 516 |
+
results.push(`${host}:${port} → ${portSuccess ? 'OPEN' : 'CLOSED'}`);
|
| 517 |
+
});
|
| 518 |
+
return results.join('<br>');
|
| 519 |
+
|
| 520 |
+
case 'HTTP Test':
|
| 521 |
+
return isSuccess ?
|
| 522 |
+
`HTTP GET ${url} → 200 OK (${(Math.random() * 300 + 100).toFixed(0)}ms)` :
|
| 523 |
+
`HTTP GET ${url} failed: Connection timed out`;
|
| 524 |
+
|
| 525 |
+
case 'Download Speed':
|
| 526 |
+
const speed = (Math.random() * 5000 + 1000).toFixed(1);
|
| 527 |
+
return isSuccess ?
|
| 528 |
+
`Estimated download speed: ${speed} KB/s (2.00 MB in ${(Math.random() * 2000 + 500).toFixed(0)}ms)` :
|
| 529 |
+
'Download test failed: Connection interrupted';
|
| 530 |
+
|
| 531 |
+
default:
|
| 532 |
+
return 'Test completed';
|
| 533 |
+
}
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
function generateRandomIP() {
|
| 537 |
+
return `${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
// Export results
|
| 541 |
+
document.getElementById('exportResultsBtn').addEventListener('click', function() {
|
| 542 |
+
const testResults = document.getElementById('testResults');
|
| 543 |
+
|
| 544 |
+
if (testResults.textContent.includes('No test results yet')) {
|
| 545 |
+
showToast('No results to export. Run tests first.', 'error');
|
| 546 |
+
return;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
// Simulate export
|
| 550 |
+
showToast('Results exported to CSV file', 'success');
|
| 551 |
+
});
|
| 552 |
+
</script>
|
| 553 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Rayan545454/sorry-1234" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 554 |
+
</html>
|