Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the new design from #649 #694

Draft
wants to merge 30 commits into
base: master
Choose a base branch
from
Draft

Implement the new design from #649 #694

wants to merge 30 commits into from

Conversation

sstidl
Copy link
Collaborator

@sstidl sstidl commented Dec 29, 2024

User description

I will close #649 to follow up here in this PR. Thanks to @Timendus there for implementing it.

The when the merge is ready we will have a new design as proposed by @Chris-ZoGo in #585


PR Type

Enhancement


Description

Implements a complete UI redesign of LibreSpeed based on fromScratch Studio's design:

  • New modern, responsive UI with improved user experience
  • Added features:
    • Server selection dropdown with sponsor information
    • Animated gauges for download/upload speeds
    • Improved display of ping and jitter
    • Share results functionality
    • Privacy policy modal
  • Docker improvements:
    • Fixed dual mode server configuration
    • Added proper frontend file copying
    • Updated Alpine configuration
  • Documentation updates for new frontend implementation
  • No dependencies and pure JS/CSS implementation
  • Maintains backwards compatibility with existing backend

Changes walkthrough 📝

Relevant files
Enhancement
3 files
index.js
Implement new UI with modern design and features                 
+397/-0 
index.html
New responsive HTML structure with modern UI elements       
+151/-0 
index.css
Main CSS file with modern styling and imports                       
+85/-0   
Bug fix
1 files
ui.php
Fix dual mode server configuration                                             
+14/-2   
Configuration changes
1 files
entrypoint.sh
Update Docker setup for new frontend                                         
+6/-3     
Additional files
16 files
Dockerfile +1/-0     
Dockerfile.alpine +2/-1     
README.md +4/-0     
docker-compose.yml +27/-0   
servers.json +409/-0 
test-script.sh +9/-2     
README.md +76/-0   
server-list.json +409/-0 
settings.json +9/-0     
button.css +83/-0   
colors.css +36/-0   
dialog.css +132/-0 
fonts.css +22/-0   
main.css +58/-0   
results.css +260/-0 
server-selector.css +171/-0 

💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

Timendus and others added 30 commits August 12, 2024 16:28
Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com>
Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com>
Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com>
This was linked to issues Dec 29, 2024
@sstidl sstidl self-assigned this Dec 29, 2024
@sstidl sstidl marked this pull request as ready for review December 29, 2024 19:47
@sstidl sstidl marked this pull request as draft December 29, 2024 19:47

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

585 - PR Code Verified

Compliant requirements:

  • Implemented new modern, responsive UI design for LibreSpeed based on fromScratch Studio's design
  • Added support for both desktop and mobile views through responsive CSS
  • Improved usability with features like server selection dropdown, animated gauges, improved display of metrics
  • Maintained backwards compatibility with existing backend

Requires further human verification:

  • Visual verification needed to ensure design matches mockups exactly
  • Browser testing needed to verify responsive behavior across devices
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

XSS:
The code directly injects HTML content from server responses into the DOM in multiple places (e.g. sponsor name/URL). This could enable XSS attacks if the server response contains malicious content. The code should sanitize this data before inserting into DOM.

⚡ Recommended focus areas for review

Error Handling

The server list and settings fetch error handling could be improved. Currently errors are only logged to console but user is not notified of issues.

try {
  const response = await fetch("settings.json");
  const settings = await response.json();
  if (!settings || typeof settings !== "object") {
    return console.error("Settings are empty or malformed");
  }
  for (let setting in settings) {
    testState.speedtest.setParameter(setting, settings[setting]);
    if (
      setting == "telemetry_level" &&
      settings[setting] &&
      settings[setting] != "off" &&
      settings[setting] != "disabled" &&
      settings[setting] != "false"
    ) {
      testState.telemetryEnabled = true;
      document.querySelector("#privacy-warning").classList.remove("hidden");
    }
  }
} catch (error) {
  console.error("Failed to fetch settings:", error);
}
Performance

The renderUI function runs on every animation frame which could impact performance. Consider using a less frequent update interval for non-critical UI updates.

function startRenderingLoop() {
  // Do these queries once to speed up the rendering itself
  const serverSelector = document.querySelector("div.server-selector");
  const selectedServer = serverSelector.querySelector("#selected-server");
  const sponsor = serverSelector.querySelector("#sponsor");
  const startButton = document.querySelector("#start-button");
  const privacyWarning = document.querySelector("#privacy-warning");

  const gauges = document.querySelectorAll("#download-gauge, #upload-gauge");
  const downloadProgress = document.querySelector("#download-gauge .progress");
  const uploadProgress = document.querySelector("#upload-gauge .progress");
  const downloadGauge = document.querySelector("#download-gauge .speed");
  const uploadGauge = document.querySelector("#upload-gauge .speed");
  const downloadText = document.querySelector("#download-gauge span");
  const uploadText = document.querySelector("#upload-gauge span");

  const pingAndJitter = document.querySelectorAll(".ping, .jitter");
  const ping = document.querySelector("#ping");
  const jitter = document.querySelector("#jitter");
  const shareResults = document.querySelector("#share-results");
  const copyLink = document.querySelector("#copy-link");
  const resultsImage = document.querySelector("#results");

  const buttonTexts = {
    [INITIALIZING]: "Loading...",
    [READY]: "Let's start",
    [RUNNING]: "Abort",
    [FINISHED]: "Restart",
  };

  // Show copy link button only if navigator.clipboard is available
  copyLink.classList.toggle("hidden", !navigator.clipboard);

  function renderUI() {
    // Make the main button reflect the current state
    startButton.textContent = buttonTexts[testState.state];
    startButton.classList.toggle("disabled", testState.state === INITIALIZING);
    startButton.classList.toggle("active", testState.state === RUNNING);

    // Disable the server selector while test is running
    serverSelector.classList.toggle("disabled", testState.state === RUNNING);

    // Show selected server
    if (testState.selectedServerDirty) {
      const server = testState.speedtest.getSelectedServer();
      selectedServer.textContent = server.name;
      if (server.sponsorName) {
        if (server.sponsorURL) {
          sponsor.innerHTML = `Sponsor: <a href="${server.sponsorURL}">${server.sponsorName}</a>`;
        } else {
          sponsor.textContent = `Sponsor: ${server.sponsorName}`;
        }
      } else {
        sponsor.innerHTML = "&nbsp;";
      }
      testState.selectedServerDirty = false;
    }

    // Activate the gauges when test running or finished
    gauges.forEach((e) =>
      e.classList.toggle(
        "enabled",
        testState.state === RUNNING || testState.state === FINISHED
      )
    );

    // Show ping and jitter if data is available
    pingAndJitter.forEach((e) =>
      e.classList.toggle(
        "hidden",
        !(
          testState.testData &&
          testState.testData.pingStatus &&
          testState.testData.jitterStatus
        )
      )
    );

    // Show share button after test if server supports it
    shareResults.classList.toggle(
      "hidden",
      !(
        testState.state === FINISHED &&
        testState.telemetryEnabled &&
        testState.testData.testId
      )
    );

    if (testState.testDataDirty) {
      // Set gauge rotations
      downloadProgress.style = `--progress-rotation: ${
        testState.testData.dlProgress * 180
      }deg`;
      uploadProgress.style = `--progress-rotation: ${
        testState.testData.ulProgress * 180
      }deg`;
      downloadGauge.style = `--speed-rotation: ${mbpsToRotation(
        testState.testData.dlStatus,
        testState.testData.testState === 1
      )}deg`;
      uploadGauge.style = `--speed-rotation: ${mbpsToRotation(
        testState.testData.ulStatus,
        testState.testData.testState === 3
      )}deg`;

      // Set numeric values
      downloadText.textContent = numberToText(testState.testData.dlStatus);
      uploadText.textContent = numberToText(testState.testData.ulStatus);
      ping.textContent = numberToText(testState.testData.pingStatus);
      jitter.textContent = numberToText(testState.testData.jitterStatus);

      // Set user's IP and provider
      if (testState.testData.clientIp) {
        privacyWarning.innerHTML = `<span>You are connected through:</span><br/>${testState.testData.clientIp}`;
        privacyWarning.classList.remove("hidden");
      }

      // Set image for sharing results
      if (testState.testData.testId) {
        resultsImage.src =
          window.location.href.substring(
            0,
            window.location.href.lastIndexOf("/")
          ) +
          "/results/?id=" +
          testState.testData.testId;
      }

      testState.testDataDirty = false;
    }

    requestAnimationFrame(renderUI);
  }

  renderUI();
}

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.

LibreSpeed Redesign UI Design Not impressive to user
3 participants