Skip to content

Conversation

@eshirvana
Copy link

@eshirvana eshirvana commented Jul 24, 2025

PR Type

Enhancement


Description

  • Complete UI redesign using TailwindCSS with modern gradient backgrounds and dark mode support

  • Replaced canvas-based gauges with animated SVG speedometer featuring gradient colors and smooth transitions

  • Refactored HTML structure to semantic layout with improved accessibility (ARIA labels, roles)

  • Enhanced IP/ISP information parsing with fallback logic for multiple data source formats

  • Added dark mode toggle with localStorage persistence and system preference detection


Diagram Walkthrough

flowchart LR
  A["Old Canvas-based UI"] -->|Replace| B["TailwindCSS Modern UI"]
  C["Canvas Gauges"] -->|Replace| D["Animated SVG Speedometer"]
  E["Basic HTML"] -->|Enhance| F["Semantic HTML with ARIA"]
  G["No Dark Mode"] -->|Add| H["Dark Mode Toggle"]
  I["Simple IP Display"] -->|Improve| J["Enhanced IP/ISP Parsing"]
Loading

File Walkthrough

Relevant files
Enhancement
index.html
Complete UI redesign with TailwindCSS and animated speedometer

index.html

  • Integrated TailwindCSS CDN with custom animation keyframes
    (pulse-slow, fade-in, spin-slow, progress-fill, glow-pulse,
    bounce-gentle)
  • Replaced canvas-based meter drawing with animated SVG speedometer
    using stroke-dasharray for progress visualization
  • Completely restructured HTML layout using semantic elements with
    TailwindCSS utility classes for responsive design
  • Added dark mode toggle button with localStorage persistence and system
    preference detection via initThemeToggle() function
  • Enhanced IP/ISP information parsing with multiple fallback formats
    (space-separated, hyphen-separated, combined formats)
  • Improved server selection UI with server info display (distance,
    sponsor) via updateServerInfo() function
  • Refactored speedometer color scheme to use gradient fills that change
    based on test type (download/upload/ping)
  • Added comprehensive ARIA labels, roles, and live regions for improved
    accessibility
  • Replaced inline CSS styles with TailwindCSS classes and removed old
    stylesheet entirely
  • Enhanced privacy policy modal with improved styling and backdrop blur
    effects
+674/-365

@eshirvana eshirvana changed the title New UI New UI with TailwindCSS Jul 24, 2025
@eshirvana
Copy link
Author

eshirvana commented Jul 24, 2025

it closes #715

@gmemstr
Copy link

gmemstr commented Jul 25, 2025

I think it's very much a case of personal preference. Personally, I really like the current UI and find it very nice and functional. I think effort would be better served building a theme swapping component or documentation around serving your own frontend (which if memory serves is more than possible).

This redesigned UI is also very... generic, which is expected given it's generated from an LLM. Ideally a designed UI is thought out and planned, rather than a mathematical average of "modern web design". There's also no indication of how it scales to smaller devices.

As a side note, looking through the PR, some other changes seem to have slipped through, like modifying the default server and instance name.

@adolfintel
Copy link
Member

adolfintel commented Jul 27, 2025

Sorry for the late reply.

The new UI is nice, but a full rewrite of the application is ongoing so it's kinda pointless to merge it now.

@eshirvana
Copy link
Author

Sorry for the late reply.

The new UI is nice, but a full rewrite of the application is ongoing so it's kinda pointless to merge it now.
@adolfintel Is there a way I can contribute on that ? Is it happening under "newdesign" branch?

@github-actions
Copy link

github-actions bot commented Dec 6, 2025

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@eshirvana eshirvana closed this Dec 8, 2025
@eshirvana eshirvana reopened this Dec 8, 2025
@eshirvana eshirvana marked this pull request as ready for review December 8, 2025 16:03
@qodo-free-for-open-source-projects
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Missing SRI verification

Description: Loading TailwindCSS from an external CDN (cdn.tailwindcss.com) without Subresource
Integrity (SRI) verification creates a supply chain vulnerability where compromised CDN
content could inject malicious code into the application.
index.html [9-9]

Referred Code
<script src="https://cdn.tailwindcss.com"></script>
<script>
Unvalidated localStorage usage

Description: Dark mode preference is stored in localStorage without validation or sanitization, and the
stored value is directly used to manipulate DOM classes, potentially allowing stored XSS
if an attacker can inject malicious values into localStorage through other
vulnerabilities.
index.html [431-451]

Referred Code
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    themeToggleLightIcon.classList.remove('hidden');
    document.documentElement.classList.add('dark');
} else {
    themeToggleDarkIcon.classList.remove('hidden');
}

themeToggleBtn.addEventListener('click', function() {
    // Toggle icons inside button
    themeToggleDarkIcon.classList.toggle('hidden');
    themeToggleLightIcon.classList.toggle('hidden');

    // Toggle dark mode
    var isDark = document.documentElement.classList.contains('dark');
    if (isDark) {
        document.documentElement.classList.remove('dark');
        localStorage.setItem('color-theme', 'light');
    } else {
        document.documentElement.classList.add('dark');
        localStorage.setItem('color-theme', 'dark');
    }
Unsafe clipboard operation

Description: The resultsURL input field uses deprecated document.execCommand('copy') with inline
onclick handler that directly selects and copies content without validation, and displays
an alert without sanitizing the copied content, potentially exposing users to clipboard
manipulation attacks.
index.html [717-723]

Referred Code
<input type="text" 
       value="" 
       id="resultsURL" 
       readonly="readonly" 
       onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')" 
       class="w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"
       placeholder="Results URL will appear here"/>
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Generic function name: Function I(i) uses a single-letter name that doesn't express its purpose of
retrieving DOM elements by ID

Referred Code
function I(i){return document.getElementById(i);}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing null checks: Function updateSpeedometer accesses DOM element without verifying it exists, though it
does return early if null

Referred Code
function updateSpeedometer(targetAmount, color, animated = true) {
	var progressCircle = document.getElementById('speed-progress');

	if (!progressCircle) return;

	// Calculate stroke-dashoffset for progress (283 is full circumference)
	var circumference = 283;
	var offset = circumference - (targetAmount * circumference);

	// Update progress circle
	progressCircle.style.strokeDashoffset = offset;

	lastProgress = targetAmount;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
IP address logging: Console logs at line 293 output raw IP addresses and ISP information which constitutes PII
that should not be logged to browser console

Referred Code
	console.log("IP parsing - Raw:", clientIpRaw, "IP:", ipAddress, "ISP:", ispName);
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated external data: User data from uiData.clientIp, uiData.ispInfo, and related fields are directly inserted
into DOM via textContent without validation or sanitization of format and content

Referred Code
var clientIpRaw = uiData.clientIp || "-";
var ipAddress = "-";
var ispName = "-";

// Parse combined IP and ISP info if it contains a separator
if(clientIpRaw && clientIpRaw !== "-") {
	if(clientIpRaw.includes(" - ")) {
		// Format: "192.168.1.1 - ISP Name"
		var parts = clientIpRaw.split(" - ");
		ipAddress = parts[0].trim();
		ispName = parts[1].trim();
	} else if(clientIpRaw.includes("-")) {
		// Format: "192.168.1.1-ISP Name" or "192.168.1.1 -ISP Name"
		var parts = clientIpRaw.split("-");
		ipAddress = parts[0].trim();
		ispName = parts[1] ? parts[1].trim() : "-";
	} else if(clientIpRaw.includes(" ") && !clientIpRaw.match(/^\d+\.\d+\.\d+\.\d+$/)) {
		// Format: "192.168.1.1 ISP Company Name"
		var parts = clientIpRaw.split(" ", 2);
		ipAddress = parts[0].trim();
		ispName = clientIpRaw.substring(parts[0].length).trim();


 ... (clipped 47 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Console logging details: Multiple console.log and console.warn statements expose internal state and debugging
information that could reveal implementation details to end users

Referred Code
		console.warn("Invalid speed value for mbpsToAmount:", s);
		return 0;
	}
	var result = 1-(1/(Math.pow(1.3,Math.sqrt(s))));
	console.log("mbpsToAmount - Input:", s, "Output:", result);
	return result;
}
function format(d){
    d=Number(d);
    if(d<10) return d.toFixed(2);
    if(d<100) return d.toFixed(1);
    return d.toFixed(0);
}

//UI CODE
var uiData=null;
function startStop(){
    if(s.getState()==3){
		//speed test is running, abort
		s.abort();
		data=null;


 ... (clipped 172 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Separate UI logic from HTML

The PR places all JavaScript logic directly within the index.html file. This
should be refactored by moving the JavaScript into a separate file, such as
ui.js, to improve code organization and maintainability.

Examples:

index.html [47-479]
<script type="text/javascript">
function I(i){return document.getElementById(i);}

//LIST OF TEST SERVERS. Leave empty if you're doing a standalone installation. See documentation for details
var SPEEDTEST_SERVERS=[
	/*{	//this server doesn't actually exist, remove it
		name:"Example Server 1", //user friendly name for the server
		server:"//test1.mydomain.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
		dlURL:"backend/garbage.php",  //path to download test on this server (garbage.php or replacement)
		ulURL:"backend/empty.php",  //path to upload test on this server (empty.php or replacement)

 ... (clipped 423 lines)

Solution Walkthrough:

Before:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.tailwindcss.com"></script>
    <script type="text/javascript" src="speedtest.js"></script>
    <script type="text/javascript">
        // ~350 lines of JavaScript logic for UI, animations, data parsing, etc.
        function initServers(){...}
        function updateSpeedometer(...) {...}
        function updateUI(forced){...}
        function initThemeToggle(){...}
        // ... and many other functions
    </script>
</head>
<body onload="initServers()">
    <!-- ~350 lines of HTML structure -->
</body>
</html>

After:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.tailwindcss.com"></script>
    <script type="text/javascript" src="speedtest.js"></script>
    <script type="text/javascript" src="ui.js" defer></script>
</head>
<body onload="initServers()">
    <!-- HTML structure remains the same -->
</body>
</html>

/* ui.js (new file) */
// ~350 lines of JavaScript logic moved from index.html
function initServers(){...}
function updateSpeedometer(...) {...}
function updateUI(forced){...}
function initThemeToggle(){...}
// ... and many other functions
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant architectural issue where all JavaScript logic is embedded in the index.html file, which harms long-term maintainability and violates the principle of separation of concerns.

Medium
General
Display correct auto-selected server info

Update the updateServerInfo function to display the details of the automatically
selected server when the "Auto Selected" option is active, rather than showing
default values.

index.html [409-417]

 var selectedIndex = serverSelect.value;
-if(selectedIndex && SPEEDTEST_SERVERS[selectedIndex]) {
-    var selectedServer = SPEEDTEST_SERVERS[selectedIndex];
+var selectedServer;
+
+if (selectedIndex === "") { // "Auto Selected"
+    selectedServer = s.getSelectedServer();
+} else if (SPEEDTEST_SERVERS[selectedIndex]) {
+    selectedServer = SPEEDTEST_SERVERS[selectedIndex];
+}
+
+if (selectedServer) {
     I("server-distance").textContent = selectedServer.distance || "-";
     I("server-sponsor").textContent = selectedServer.sponsor || "LibreSpeed";
 } else {
     I("server-distance").textContent = "-";
     I("server-sponsor").textContent = "LibreSpeed";
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the UI does not show details for the auto-selected server and proposes a valid improvement to enhance user experience by displaying this information.

Medium
Remove debugging console logs

Remove the debugging console.log statement from the mbpsToAmount function, and
other similar logs added in the PR, as they are not suitable for production
code.

index.html [204-206]

 var result = 1-(1/(Math.pow(1.3,Math.sqrt(s))));
-console.log("mbpsToAmount - Input:", s, "Output:", result);
 return result;
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that multiple debugging console.log statements were added, which should be removed from production code to avoid cluttering the console.

Low
Possible issue
Improve IP and ISP parsing

To prevent incorrect parsing of ISP names that contain separators, modify the
IP/ISP parsing logic to split the string only on the first occurrence of a
separator.

index.html [273-291]

-if(clientIpRaw.includes(" - ")) {
-    // Format: "192.168.1.1 - ISP Name"
-    var parts = clientIpRaw.split(" - ");
+var parts;
+if (clientIpRaw.includes(" - ")) {
+    parts = clientIpRaw.split(" - ", 2);
     ipAddress = parts[0].trim();
     ispName = parts[1].trim();
-} else if(clientIpRaw.includes("-")) {
-    // Format: "192.168.1.1-ISP Name" or "192.168.1.1 -ISP Name"
-    var parts = clientIpRaw.split("-");
+} else if (clientIpRaw.includes("-")) {
+    parts = clientIpRaw.split("-", 2);
     ipAddress = parts[0].trim();
     ispName = parts[1] ? parts[1].trim() : "-";
-} else if(clientIpRaw.includes(" ") && !clientIpRaw.match(/^\d+\.\d+\.\d+\.\d+$/)) {
-    // Format: "192.168.1.1 ISP Company Name"
-    var parts = clientIpRaw.split(" ", 2);
+} else if (clientIpRaw.includes(" ") && !clientIpRaw.match(/^\d+\.\d+\.\d+\.\d+$/)) {
+    parts = clientIpRaw.split(" ", 2);
     ipAddress = parts[0].trim();
-    ispName = clientIpRaw.substring(parts[0].length).trim();
+    ispName = parts[1] ? parts[1].trim() : "-";
 } else {
-    // Assume it's just the IP address
     ipAddress = clientIpRaw;
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a potential parsing bug for ISP names containing separators and provides a robust fix by limiting the split operation.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants