Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c1af45a
Implement fromScratch design
Timendus Jul 30, 2024
c7e3fcc
Add some documentation
Timendus Jul 30, 2024
a6e5241
enable new design in docker (no alpine yet)
sstidl Oct 19, 2024
66e967a
Merge branch 'master' of github.com:librespeed/speedtest into newdesign
sstidl Oct 19, 2024
4b6a8b8
merge fixed docker images
sstidl Oct 26, 2024
e8c8ac8
alpine docker added new design
sstidl Oct 26, 2024
74644b0
Merge branch 'master' of https://github.com/librespeed/speedtest into…
sstidl Oct 26, 2024
a02f4e3
fix #685
sstidl Nov 30, 2024
0642af8
Implement fromScratch design
Timendus Dec 1, 2024
1dc40d7
Add some documentation
Timendus Dec 1, 2024
853cb4f
enable new design in docker (no alpine yet)
sstidl Dec 1, 2024
96b0261
alpine docker added new design
sstidl Dec 1, 2024
cc4c2f8
fix #685
sstidl Dec 1, 2024
018c696
Merge branch 'newdesign' of https://github.com/librespeed/speedtest i…
stefanstidlffg Dec 1, 2024
83f7491
fix database permissions alpine, remove baby
sstidl Dec 1, 2024
b345343
Merge remote-tracking branch 'origin/master' into newdesign
sstidl Dec 1, 2024
13f6c49
Merge commit 'dd40a3a4937d88fdc2337adb190fb9d091e849a4' into newdesign
sstidl Dec 29, 2024
f06187a
Merge branch 'fix-docker-images' into newdesign
sstidl Dec 29, 2024
dda7841
hide serverselector on only one server
sstidl Dec 29, 2024
4fc0932
Update frontend/styling/server-selector.css
sstidl Apr 22, 2025
7a11454
Merge branch 'master' into newdesign
sstidl Dec 6, 2025
b1a5cea
Merge commit 'fc2dc5125d60280066e368417534bfe762068156' into newdesign
stefanstidlffg Dec 6, 2025
61ec779
fix alpine image again
stefanstidlffg Dec 6, 2025
ec7b0ab
Merge remote-tracking branch 'orig/master' into newdesign
stefanstidlffg Dec 6, 2025
b1111e5
adjust settings.json in entrypoint
sstidl Dec 7, 2025
3a0e6b3
Update frontend/javascript/index.js
sstidl Dec 7, 2025
235fa63
Add feature switch for new design via config file, URL parameters, an…
Copilot Dec 29, 2025
cb79f72
add armv7
stefanstidlffg Dec 29, 2025
0570377
reformat
stefanstidlffg Dec 29, 2025
119eb1f
Add GDPR_EMAIL environment variable for Docker deployments (#743)
Copilot Dec 29, 2025
48af9e8
cleanup old EMAIL ENV Var
sstidl Dec 29, 2025
a41566f
fix: line break in html prevented sed replacement
sstidl Dec 29, 2025
007a050
version 6.0.0pre1
sstidl Dec 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@
"addEventListener": "readonly"
},
"rules": {
"no-unused-vars": ["warn", { "args": "none" }],
"no-unused-vars": [
"warn",
{
"args": "none"
}
],
"no-console": "off",
"no-empty": "warn",
"no-undef": "warn",
"no-const-assign": "error"
}
}
}
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down
2 changes: 1 addition & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"arrowParens": "avoid",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore"
}
}
96 changes: 96 additions & 0 deletions DESIGN_SWITCH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Design Feature Switch

LibreSpeed now supports switching between the classic design and the new modern design.

## Default Behavior

By default, LibreSpeed uses the **classic design** (located in `index-classic.html`).

## Architecture

### File Structure (Non-Docker)
- **`index.html`** - Entry point (lightweight switcher)
- **`index-classic.html`** - Classic design at root
- **`index-modern.html`** - Modern design at root (references assets in subdirectories)
- **`frontend/`** - Directory containing modern design assets (CSS, JS, images, fonts) - kept for non-Docker deployments

### File Structure (Docker)
In Docker deployments, the frontend assets are flattened to root-level subdirectories:
- **`index.html`** - Entry point (lightweight switcher)
- **`index-classic.html`** - Classic design
- **`index-modern.html`** - Modern design
- **`styling/`** - CSS files for modern design
- **`javascript/`** - JS files for modern design
- **`images/`** - Images for modern design
- **`fonts/`** - Fonts for modern design
- **No `frontend/` directory** - Assets are copied directly to root subdirectories

### Benefits of Root-Level Design Files
✅ Both designs at same level - no path confusion
✅ `results/` accessible from both designs with same relative path
✅ `backend/` accessible from both designs with same relative path
✅ No subdirectory nesting issues
✅ Clean separation of concerns
✅ Docker containers have no `frontend/` parent directory

## Browser Compatibility

The feature switch uses modern JavaScript features (URLSearchParams, fetch API). It is compatible with all modern browsers. The new design itself requires modern browser features and has no backwards compatibility with older browsers (see `frontend/README.md`).

## Enabling the New Design

There are two ways to enable the new design:

### Method 1: Configuration File (Persistent)

Edit the `config.json` file in the root directory and set `useNewDesign` to `true`:

```json
{
"useNewDesign": true
}
```

This will make the new design the default for all users visiting your site.

### Method 2: URL Parameter (Temporary Override)

You can override the configuration by adding a URL parameter:

- To use the new design: `http://yoursite.com/?design=new`
- To use the old design: `http://yoursite.com/?design=old`

URL parameters take precedence over the configuration file, making them useful for testing or allowing users to choose their preferred design.

## Design Locations

### Non-Docker Deployments
- **Entry Point**: Root `index.html` file (lightweight redirect page)
- **Old Design**: `index-classic.html` at root
- **New Design**: `index-modern.html` at root (references assets in `frontend/` subdirectory)
- **Assets**: Frontend assets (CSS, JS, images, fonts) in `frontend/` subdirectory

### Docker Deployments
- **Entry Point**: Root `index.html` file (lightweight redirect page)
- **Old Design**: `index-classic.html` at root
- **New Design**: `index-modern.html` at root (references assets in root subdirectories)
- **Assets**: Frontend assets copied directly to root subdirectories (`styling/`, `javascript/`, `images/`, `fonts/`)
- **No `frontend/` directory** - Assets are flattened to root level

Both designs are at the same directory level, ensuring that relative paths to shared resources like `backend/` and `results/` work correctly for both.

## Technical Details

The feature switch is implemented in `design-switch.js`, which is loaded by the root `index.html`. It checks:

1. First, URL parameters (`?design=new` or `?design=old`)
2. Then, the `config.json` configuration file
3. Redirects to either `index-classic.html` or `index-modern.html`

Both design HTML files are at the root level, eliminating path issues.

### Non-Docker
The modern design references assets from the `frontend/` subdirectory (e.g., `frontend/styling/index.css`), while both designs can access shared resources like `backend/` and `results/` using the same relative paths.

### Docker
In Docker deployments, the `frontend/` directory is flattened during container startup. Assets are copied directly to root-level subdirectories (`styling/`, `javascript/`, `images/`, `fonts/`), and `index-modern.html` references these root-level paths. This eliminates the `frontend/` parent directory in the container.
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@

# Copy sources
COPY backend/ /speedtest/backend
COPY frontend/ /speedtest/frontend

COPY results/*.php /speedtest/results/
COPY results/*.ttf /speedtest/results/

COPY *.js /speedtest/
COPY index.html /speedtest/
COPY index-classic.html /speedtest/
COPY index-modern.html /speedtest/
COPY config.json /speedtest/
COPY favicon.ico /speedtest/

COPY docker/servers.json /servers.json
Expand All @@ -30,11 +35,12 @@
# Prepare default environment variables
ENV TITLE=LibreSpeed
ENV MODE=standalone
ENV PASSWORD=password

Check warning on line 38 in Dockerfile

View workflow job for this annotation

GitHub Actions / build (./Dockerfile, ghcr.io/librespeed/speedtest)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 38 in Dockerfile

View workflow job for this annotation

GitHub Actions / build (./Dockerfile, ghcr.io/librespeed/speedtest)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV TELEMETRY=false
ENV ENABLE_ID_OBFUSCATION=false
ENV REDACT_IP_ADDRESSES=false
ENV WEBPORT=8080
ENV USE_NEW_DESIGN=false

# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
STOPSIGNAL SIGWINCH
Expand Down
6 changes: 6 additions & 0 deletions Dockerfile.alpine
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@

# Copy sources
COPY backend/ /speedtest/backend
COPY frontend/ /speedtest/frontend

COPY results/*.php /speedtest/results/
COPY results/*.ttf /speedtest/results/

COPY *.js /speedtest/
COPY index.html /speedtest/
COPY index-classic.html /speedtest/
COPY index-modern.html /speedtest/
COPY config.json /speedtest/
COPY favicon.ico /speedtest/

COPY docker/servers.json /servers.json
Expand All @@ -44,11 +49,12 @@
# Prepare default environment variables
ENV TITLE=LibreSpeed
ENV MODE=standalone
ENV PASSWORD=password

Check warning on line 52 in Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / build (./Dockerfile.alpine, ghcr.io/librespeed/speedtest, -alpine)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 52 in Dockerfile.alpine

View workflow job for this annotation

GitHub Actions / build (./Dockerfile.alpine, ghcr.io/librespeed/speedtest, -alpine)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV TELEMETRY=false
ENV ENABLE_ID_OBFUSCATION=false
ENV REDACT_IP_ADDRESSES=false
ENV WEBPORT=8080
ENV USE_NEW_DESIGN=false

# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
STOPSIGNAL SIGWINCH
Expand Down
3 changes: 3 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"useNewDesign": false
}
72 changes: 72 additions & 0 deletions design-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Feature switch for enabling the new LibreSpeed design
*
* This script checks for:
* 1. URL parameter: ?design=new or ?design=old
* 2. Configuration file: config.json with useNewDesign flag
*
* Default behavior: Shows the old design
*
* Note: This script is only loaded on the root index.html
*/
(function () {
'use strict';

// Don't run this script if we're already on a specific design page
// This prevents infinite redirect loops
const currentPath = window.location.pathname;
if (currentPath.includes('index-classic.html') || currentPath.includes('index-modern.html')) {
return;
}

// Check URL parameters first (they override config)
const urlParams = new URLSearchParams(window.location.search);
const designParam = urlParams.get('design');

if (designParam === 'new') {
redirectToNewDesign();
return;
}

if (designParam === 'old' || designParam === 'classic') {
redirectToOldDesign();
return;
}

// Check config.json for design preference
try {
const xhr = new XMLHttpRequest();
// Use a synchronous request to prevent a flash of the old design before redirecting
xhr.open('GET', 'config.json', false);
xhr.send(null);

// Check for a successful response, but not 304 Not Modified, which can have an empty response body
if (xhr.status >= 200 && xhr.status < 300) {
const config = JSON.parse(xhr.responseText);
if (config.useNewDesign === true) {
redirectToNewDesign();
} else {
redirectToOldDesign();
}
} else {
// Config not found or error - default to old design
redirectToOldDesign();
}
} catch (error) {
// If there's any error (e.g., network, JSON parse), default to old design
console.log('Using default (old) design:', error.message || 'config error');
redirectToOldDesign();
}

function redirectToNewDesign() {
// Preserve any URL parameters when redirecting
const currentParams = window.location.search;
window.location.href = 'index-modern.html' + currentParams;
}

function redirectToOldDesign() {
// Preserve any URL parameters when redirecting
const currentParams = window.location.search;
window.location.href = 'index-classic.html' + currentParams;
}
})();
2 changes: 1 addition & 1 deletion doc.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# LibreSpeed

> by Federico Dossena
> Version 5.4.1
> Version 6.0.0pre1
> [https://github.com/librespeed/speedtest/](https://github.com/librespeed/speedtest/)

## Introduction
Expand Down
5 changes: 3 additions & 2 deletions doc_docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ services:
#ENABLE_ID_OBFUSCATION: "false"
#REDACT_IP_ADDRESSES: "false"
#PASSWORD:
#EMAIL:
#GDPR_EMAIL: "privacy@example.com"
#DISABLE_IPINFO: "false"
#IPINFO_APIKEY: "your api key"
#DISTANCE: "km"
Expand All @@ -57,6 +57,7 @@ The test can be accessed on port 80.
Here's a list of additional environment variables available in this mode:

* __`TITLE`__: Title of your speed test. Default value: `LibreSpeed`
* __`USE_NEW_DESIGN`__: When set to `true`, enables the new modern frontend design. When set to `false` (default), uses the classic design. The design can also be switched using URL parameters (`?design=new` or `?design=old`). Default value: `false`
* __`TELEMETRY`__: Whether to enable telemetry or not. If enabled, you maybe want your data to be persisted. See below. Default value: `false`
* __`ENABLE_ID_OBFUSCATION`__: When set to true with telemetry enabled, test IDs are obfuscated, to avoid exposing the database internal sequential IDs. Default value: `false`
* __`OBFUSCATION_SALT`__: The salt string that is used to obfuscate the test IDs. The format shoud be a 2 byte hex string (e.g. `0x1234abcd`). If not specified, a random one will be generated.
Expand All @@ -70,7 +71,7 @@ Here's a list of additional environment variables available in this mode:
* DB_USERNAME, DB_PASSWORD - credentials of the user with read and update permissions to the db
* mssql - not supported in docker image yet (feel free to open a PR with that, has to be done in `entrypoint.sh`)
* __`PASSWORD`__: Password to access the stats page. If not set, stats page will not allow accesses.
* __`EMAIL`__: Email address for GDPR requests. Must be specified when telemetry is enabled.
* __`GDPR_EMAIL`__: Email address displayed in the privacy policy for data deletion requests. If not set, the default placeholder text will be shown. This should be set to comply with GDPR requirements when running in production. Must be specified when telemetry is enabled.
* __`DISABLE_IPINFO`__: If set to `true`, ISP info and distance will not be fetched from either [ipinfo.io](https://ipinfo.io) or the offline database. Default: value: `false`
* __`IPINFO_APIKEY`__: API key for [ipinfo.io](https://ipinfo.io). Optional, but required if you want to use the full [ipinfo.io](https://ipinfo.io) APIs (required for distance measurement)
* __`DISTANCE`__: When `DISABLE_IPINFO` is set to false, this specifies how the distance from the server is measured. Can be either `km` for kilometers, `mi` for miles, or an empty string to disable distance measurement. Requires an [ipinfo.io](https://ipinfo.io) API key. Default value: `km`
Expand Down
56 changes: 52 additions & 4 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
#!/bin/bash

echo "Setting up docker env..."
echo "MODE: $MODE"
echo "USE_NEW_DESIGN: $USE_NEW_DESIGN"
echo "WEBPORT: $WEBPORT"
echo "REDACT_IP_ADDRESSES: $REDACT_IP_ADDRESSES"
echo "DB_TYPE: $DB_TYPE"
echo "ENABLE_ID_OBFUSCATION: $ENABLE_ID_OBFUSCATION"
echo "GDPR_EMAIL: $GDPR_EMAIL"

set -e
set -x
#set -x

is_alpine() {
[ -f /etc/alpine-release ]
Expand All @@ -13,6 +22,10 @@ rm -rf /var/www/html/*
# Copy frontend files
cp /speedtest/*.js /var/www/html/

# Copy design switch files
cp /speedtest/config.json /var/www/html/
cp /speedtest/design-switch.js /var/www/html/

# Copy favicon
cp /speedtest/favicon.ico /var/www/html/

Expand Down Expand Up @@ -40,14 +53,49 @@ if [ "$MODE" == "backend" ]; then
fi
fi

# Set up unified index.php
if [ "$MODE" != "backend" ]; then
cp /speedtest/ui.php /var/www/html/index.php
# Set up index.php for frontend-only or standalone modes
if [[ "$MODE" == "frontend" || "$MODE" == "dual" || "$MODE" == "standalone" ]]; then
# Copy design files (switcher + both designs)
cp /speedtest/index.html /var/www/html/
cp /speedtest/index-classic.html /var/www/html/
cp /speedtest/index-modern.html /var/www/html/

# Copy frontend assets directly to root-level subdirectories (no frontend/ parent dir)
mkdir -p /var/www/html/styling /var/www/html/javascript /var/www/html/images /var/www/html/fonts
cp -a /speedtest/frontend/styling/* /var/www/html/styling/
cp -a /speedtest/frontend/javascript/* /var/www/html/javascript/
cp -a /speedtest/frontend/images/* /var/www/html/images/
cp -a /speedtest/frontend/fonts/* /var/www/html/fonts/ 2>/dev/null || true

# Copy frontend config files
cp /speedtest/frontend/settings.json /var/www/html/settings.json 2>/dev/null || true
if [ ! -f /var/www/html/server-list.json ]; then
echo "no server-list.json found, create one for local host"
# generate config for just the local server
echo '[{"name":"local","server":"/backend", "dlURL": "garbage.php", "ulURL": "empty.php", "pingURL": "empty.php", "getIpURL": "getIP.php", "sponsorName": "", "sponsorURL": "", "id":1 }]' > /var/www/html/server-list.json
fi

# Replace GDPR email placeholder if GDPR_EMAIL is set
if [ ! -z "$GDPR_EMAIL" ]; then
# Escape special sed characters: & (replacement), / (delimiter), \ (escape), $ (variable)
GDPR_EMAIL_ESCAPED=$(printf '%s\n' "$GDPR_EMAIL" | sed 's/[&/\\]/\\&/g; s/\$/\\$/g')

for html_file in /var/www/html/index-modern.html /var/www/html/index-classic.html; do
if [ -f "$html_file" ]; then
sed -i "s/TO BE FILLED BY DEVELOPER/$GDPR_EMAIL_ESCAPED/g; s/PUT@YOUR_EMAIL.HERE/$GDPR_EMAIL_ESCAPED/g" "$html_file"
fi
done
fi
fi
# Configure design preference via config.json
if [ "$USE_NEW_DESIGN" == "true" ]; then
sed -i 's/"useNewDesign": false/"useNewDesign": true/' /var/www/html/config.json
fi

# Apply Telemetry settings when running in standalone or frontend mode and telemetry is enabled
if [[ "$TELEMETRY" == "true" && ("$MODE" == "frontend" || "$MODE" == "standalone" || "$MODE" == "dual") ]]; then
cp -r /speedtest/results /var/www/html/results
sed -i 's/telemetry_level": ".*"/telemetry_level": "basic"/' /var/www/html/settings.json

if [ "$MODE" == "frontend" ]; then
mkdir /var/www/html/backend
Expand Down
Loading