mirror of
https://github.com/librespeed/speedtest.git
synced 2026-10-21 07:40:50 +00:00
Merge pull request #834 from BKPepe/fix/modern-frontend-asset-paths
fix: preserve the modern frontend layout in deployments (#834) Serve the modern UI assets from frontend/ in both repository and Docker deployments, matching the paths used by index-modern.html. Move settings.json and server-list.json to the web root, where the modern page resolves its configuration, and keep Docker, installation guidance, and design-switch documentation consistent with that layout. Add E2E coverage for: - serving the repository unchanged with Python's static HTTP server; - required modern UI assets and a real 404 for missing paths; - Alpine standalone settings and modern frontend rendering. Fixes #804
This commit is contained in:
commit
f3df5db6ab
16 changed files with 106 additions and 43 deletions
|
|
@ -15,23 +15,19 @@ By default, LibreSpeed uses the **classic design** (located in `index-classic.ht
|
|||
- **`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:
|
||||
Docker deployments preserve the same layout as non-Docker deployments:
|
||||
- **`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
|
||||
- **`index-classic.html`** - Classic design at root
|
||||
- **`index-modern.html`** - Modern design at root
|
||||
- **`frontend/`** - Modern design assets, copied into the web root unchanged
|
||||
- **`settings.json` and `server-list.json`** - Configuration files at root, next to `index-modern.html`
|
||||
|
||||
### 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
|
||||
### Benefits of the Shared Layout
|
||||
✅ Docker and non-Docker deployments use the same paths
|
||||
✅ Both designs are at the same level
|
||||
✅ `results/` and `backend/` use the same relative paths from both designs
|
||||
✅ The modern design loads assets consistently from `frontend/`
|
||||
✅ Configuration files stay at the web root, where the modern page expects them
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
|
|
@ -73,9 +69,9 @@ URL parameters take precedence over the configuration file, making them useful f
|
|||
### 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
|
||||
- **New Design**: `index-modern.html` at root (references assets in `frontend/` subdirectory)
|
||||
- **Assets**: Frontend assets in `frontend/`, copied into the web root unchanged
|
||||
- Same layout as a non-Docker deployment, so the two cannot drift apart
|
||||
|
||||
Both designs are at the same directory level, ensuring that relative paths to shared resources like `backend/` and `results/` work correctly for both.
|
||||
|
||||
|
|
@ -93,4 +89,4 @@ Both design HTML files are at the root level, eliminating path issues.
|
|||
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.
|
||||
In Docker deployments, `frontend/` is copied into the web root as it stands during container startup, so the container serves the same layout the repository has and the same paths `index-modern.html` asks for.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ COPY index.html /speedtest/
|
|||
COPY index-classic.html /speedtest/
|
||||
COPY index-modern.html /speedtest/
|
||||
COPY config.json /speedtest/
|
||||
COPY settings.json /speedtest/
|
||||
COPY server-list.json /speedtest/
|
||||
COPY stability.html /speedtest/
|
||||
COPY favicon.ico /speedtest/
|
||||
COPY manifest.webmanifest /speedtest/
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ COPY index.html /speedtest/
|
|||
COPY index-classic.html /speedtest/
|
||||
COPY index-modern.html /speedtest/
|
||||
COPY config.json /speedtest/
|
||||
COPY settings.json /speedtest/
|
||||
COPY server-list.json /speedtest/
|
||||
COPY stability.html /speedtest/
|
||||
COPY favicon.ico /speedtest/
|
||||
COPY manifest.webmanifest /speedtest/
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ Works with mobile versions too.
|
|||
Assuming you have PHP and a web server installed, the installation steps are quite simple.
|
||||
|
||||
1. Download the source code and extract it
|
||||
1. Copy the project files to your web server's shared folder (ie. `/var/www/html/speedtest` for Apache). For the current layout, the web root should contain `index.html`, `index-classic.html`, `index-modern.html`, `stability.html`, `design-switch.js`, `config.json`, `speedtest.js`, `speedtest_worker.js`, `stability_worker.js`, `favicon.ico`, and the `backend` folder.
|
||||
1. Also copy the contents of `frontend/` into the same web root so the modern UI assets end up in `styling/`, `javascript/`, `images/`, and `fonts/` next to the HTML files.
|
||||
1. Copy the project files to your web server's shared folder (ie. `/var/www/html/speedtest` for Apache), keeping the layout as it is in the repository. The modern UI loads its assets from `frontend/`, so that directory is copied as a whole rather than unpacked.
|
||||
1. Optionally, copy the results folder too, and set up the database using the config file in it.
|
||||
1. Be sure your permissions allow read and execute access where needed.
|
||||
1. Visit YOURSITE/speedtest/index.html and voila!
|
||||
|
|
|
|||
6
doc.md
6
doc.md
|
|
@ -197,18 +197,20 @@ Requirements:
|
|||
* FreeType 2 and its PHP module
|
||||
* The PHP gd library
|
||||
|
||||
To install the speed test frontend, copy the project files to your web server and keep the modern UI assets next to the HTML files:
|
||||
To install the speed test frontend, copy the project files to your web server, keeping the layout they have in the repository:
|
||||
|
||||
* `index.html`
|
||||
* `index-classic.html`
|
||||
* `index-modern.html`
|
||||
* `design-switch.js`
|
||||
* `config.json`
|
||||
* `settings.json`
|
||||
* `server-list.json`
|
||||
* `speedtest.js`
|
||||
* `speedtest_worker.js`
|
||||
* `favicon.ico`
|
||||
* the `backend` folder
|
||||
* the contents of `frontend/`, copied so `styling/`, `javascript/`, `images/`, and `fonts/` sit next to the HTML files
|
||||
* the `frontend` folder, copied as a whole: the modern UI loads its assets from `frontend/`
|
||||
* Optionally, the `results` folder
|
||||
|
||||
__Important:__ The speed test needs read and execute permissions in the installation folder where applicable!
|
||||
|
|
|
|||
|
|
@ -82,15 +82,12 @@ if [[ "$MODE" == "frontend" || "$MODE" == "dual" || "$MODE" == "standalone" ]];
|
|||
cp /speedtest/index-classic.html /var/www/html/
|
||||
cp /speedtest/index-modern.html /var/www/html/
|
||||
cp /speedtest/stability.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
|
||||
# Keep the frontend assets under frontend/, which is where the HTML looks for
|
||||
# them and where they sit in the repository
|
||||
cp -a /speedtest/frontend /var/www/html/
|
||||
|
||||
# Copy frontend config files
|
||||
cp /speedtest/frontend/settings.json /var/www/html/settings.json 2>/dev/null || true
|
||||
cp /speedtest/settings.json /var/www/html/settings.json 2>/dev/null || true
|
||||
if [ -f /servers.json ]; then
|
||||
echo "using mounted /servers.json for server-list.json"
|
||||
cp /servers.json /var/www/html/server-list.json
|
||||
|
|
|
|||
|
|
@ -5,15 +5,18 @@ This directory contains the modern LibreSpeed UI assets.
|
|||
## Deployment
|
||||
|
||||
For installation and deployment, follow the top-level [README.md](../README.md)
|
||||
and [DESIGN_SWITCH.md](../DESIGN_SWITCH.md). In non-Docker deployments, the
|
||||
contents of this directory must be copied so `styling/`, `javascript/`,
|
||||
`images/`, and `fonts/` sit next to the root HTML files.
|
||||
and [DESIGN_SWITCH.md](../DESIGN_SWITCH.md). This directory is copied as a
|
||||
whole: `index-modern.html` loads its assets from `frontend/`, so the layout
|
||||
here is the layout that gets served.
|
||||
|
||||
## Configuration
|
||||
|
||||
- `server-list.json` contains the default server list used by the modern UI.
|
||||
- `settings.json` overrides selected `speedtest_worker.js` settings.
|
||||
- `index.html` and `index-modern.html` show how the frontend is wired up.
|
||||
The two configuration files live at the top level rather than here, because the
|
||||
page fetches them relative to itself:
|
||||
|
||||
- `../server-list.json` contains the default server list used by the modern UI.
|
||||
- `../settings.json` overrides selected `speedtest_worker.js` settings.
|
||||
- `index.html` and `../index-modern.html` show how the frontend is wired up.
|
||||
|
||||
## Notes
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="description"
|
||||
content="Free and Open Source Speedtest. Run it right now in your browser, or self-host on a PHP, Golang, Rust or Node server. License: LGPL." />
|
||||
<link rel="shortcut icon" href="images/favicon.svg" />
|
||||
<link rel="shortcut icon" href="frontend/images/favicon.svg" />
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="images/icon-192.png" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
|
@ -15,14 +15,14 @@
|
|||
// Set this to a different URL to load the server list from another location.
|
||||
var SPEEDTEST_SERVERS = "server-list.json";
|
||||
</script>
|
||||
<script type="text/javascript" src="javascript/index.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="styling/index.css" />
|
||||
<script type="text/javascript" src="frontend/javascript/index.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="frontend/styling/index.css" />
|
||||
<title>LibreSpeed - Free and Open Source Speedtest</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.svg" alt="LibreSpeed" />
|
||||
<img src="frontend/images/logo.svg" alt="LibreSpeed" />
|
||||
</header>
|
||||
<main>
|
||||
<h1>Free and Open Source Speedtest.</h1>
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
<div class="server-selector">
|
||||
<div class="chosen">
|
||||
<div class="chevron">
|
||||
<img src="images/chevron.svg" alt="select..." />
|
||||
<img src="frontend/images/chevron.svg" alt="select..." />
|
||||
</div>
|
||||
<p>current server</p>
|
||||
<h2 id="selected-server">searching nearest server...</h2>
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
|
||||
<dialog id="share">
|
||||
<div class="close-dialog">
|
||||
<img src="images/close-button.svg" alt="Close" />
|
||||
<img src="frontend/images/close-button.svg" alt="Close" />
|
||||
</div>
|
||||
<img id="results" src="" alt="Test results in graphical form" />
|
||||
<button id="copy-link">Copy link</button>
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
|
||||
<dialog id="privacy">
|
||||
<div class="close-dialog">
|
||||
<img src="images/close-button.svg" alt="Close" />
|
||||
<img src="frontend/images/close-button.svg" alt="Close" />
|
||||
</div>
|
||||
<section>
|
||||
<h1>Privacy Policy</h1>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ module.exports = defineConfig({
|
|||
},
|
||||
globalSetup: require.resolve('./tests/e2e/global-setup.js'),
|
||||
globalTeardown: require.resolve('./tests/e2e/global-teardown.js'),
|
||||
webServer: {
|
||||
command: 'python3 -m http.server 18184 --bind 127.0.0.1 --directory .',
|
||||
url: 'http://127.0.0.1:18184',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,17 @@ services:
|
|||
ports:
|
||||
- "18185:8080"
|
||||
|
||||
standalone-alpine:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile.alpine
|
||||
environment:
|
||||
- MODE=standalone
|
||||
- WEBPORT=8080
|
||||
- USE_NEW_DESIGN=true
|
||||
ports:
|
||||
- "18187:8080"
|
||||
|
||||
standalone-apostrophe:
|
||||
build:
|
||||
context: ..
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ module.exports = async () => {
|
|||
const timeoutMs = 180_000;
|
||||
await waitForReady('standalone', 'http://127.0.0.1:18180/index.html', timeoutMs);
|
||||
await waitForReady('standalone-new', 'http://127.0.0.1:18185/index.html', timeoutMs);
|
||||
await waitForReady('standalone-alpine', 'http://127.0.0.1:18187/index.html', timeoutMs);
|
||||
await waitForReady('backend', 'http://127.0.0.1:18181/empty.php', timeoutMs);
|
||||
await waitForReady('frontend', 'http://127.0.0.1:18182/index-modern.html', timeoutMs);
|
||||
await waitForReady('dual', 'http://127.0.0.1:18183/index-modern.html', timeoutMs);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const baseUrls = {
|
|||
dual: 'http://127.0.0.1:18183',
|
||||
standaloneNew: 'http://127.0.0.1:18185',
|
||||
standaloneApostrophe: 'http://127.0.0.1:18186',
|
||||
standaloneAlpine: 'http://127.0.0.1:18187',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ test.describe('Runtime mode smoke coverage', () => {
|
|||
await expect(page.locator('main > p.tagline')).toHaveText(defaultTagline);
|
||||
});
|
||||
|
||||
test('Alpine standalone serves modern frontend settings', async ({ page, request }) => {
|
||||
const settings = await request.get(`${baseUrls.standaloneAlpine}/settings.json`);
|
||||
expect(settings.ok()).toBeTruthy();
|
||||
await expect(settings.json()).resolves.toMatchObject({ telemetry_level: 'off', time_dl_max: 12 });
|
||||
|
||||
await page.goto(`${baseUrls.standaloneAlpine}/index-modern.html`);
|
||||
await expect(modernStartButton(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('backend exposes only local backend contract endpoints', async ({ request }) => {
|
||||
for (const endpoint of ['/empty.php', '/garbage.php', '/getIP.php']) {
|
||||
const response = await request.get(`${baseUrls.backend}${endpoint}`);
|
||||
|
|
|
|||
35
tests/e2e/static-repository-assets.spec.js
Normal file
35
tests/e2e/static-repository-assets.spec.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
const staticRepositoryUrl = "http://127.0.0.1:18184";
|
||||
|
||||
const expectedAssets = [
|
||||
"/index-modern.html",
|
||||
"/settings.json",
|
||||
"/server-list.json",
|
||||
"/frontend/styling/index.css",
|
||||
"/frontend/javascript/index.js",
|
||||
"/frontend/images/logo.svg",
|
||||
"/frontend/images/favicon.svg",
|
||||
"/frontend/images/close-button.svg",
|
||||
"/frontend/images/chevron.svg",
|
||||
"/frontend/fonts/Inter-latin.woff2",
|
||||
"/frontend/fonts/Inter-latin-ext.woff2",
|
||||
"/frontend/images/background.jpeg",
|
||||
"/speedtest.js",
|
||||
"/speedtest_worker.js",
|
||||
"/design-switch.js",
|
||||
"/config.json",
|
||||
"/images/icon-192.png"
|
||||
];
|
||||
|
||||
test.describe("Unmodified repository static assets", () => {
|
||||
test("serves every modern frontend dependency and returns 404 for a missing path", async ({ request }) => {
|
||||
for (const path of expectedAssets) {
|
||||
const response = await request.get(`${staticRepositoryUrl}${path}`);
|
||||
expect(response.ok(), `${path} should return 2xx`).toBeTruthy();
|
||||
}
|
||||
|
||||
const missing = await request.get(`${staticRepositoryUrl}/does-not-exist`);
|
||||
expect(missing.status()).toBe(404);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue