ref(frontend): Replace Bootstrap with Tailwind CSS

Swap the Bootstrap CDN for a self-hosted Tailwind CSS build, mirroring
the myice setup: standalone Tailwind CLI v3.4.17, a one-off build-css.sh
script, and a gitignored generated style.css.

- Add tailwind.config.js, style-input.css, build-css.sh at repo root
- Rewrite templates/index.html: drop Bootstrap link/script, link
  /style.css, convert all classes to Tailwind utilities; showAlert now
  uses a class lookup so the JIT scanner detects dynamic classes
- Add /style.css FileResponse route in main.py; drop cdn.jsdelivr.net
  from script-src/style-src in the CSP
- Flip test assertion to assert cdn.jsdelivr.net is absent from CSP

The generated style.css and tailwindcss binary are gitignored; run
./build-css.sh to regenerate (or --watch for dev).
This commit is contained in:
2026-07-08 14:10:37 +02:00
parent 1a807cfeb4
commit 9c7f2bde11
7 changed files with 100 additions and 34 deletions
+7 -2
View File
@@ -335,8 +335,8 @@ async def read_root():
)
csp = (
"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}' https://cdn.jsdelivr.net; "
"style-src 'self' https://cdn.jsdelivr.net; "
f"script-src 'self' 'nonce-{nonce}'; "
"style-src 'self'; "
"connect-src 'self' https://login.infomaniak.com https://api.coingecko.com; "
"img-src 'self'; "
"object-src 'none'; "
@@ -389,6 +389,11 @@ async def favicon():
"""Serve the favicon"""
return FileResponse("templates/favicon.ico")
@app.get("/style.css")
async def stylesheet():
"""Serve the Tailwind-generated stylesheet"""
return FileResponse("style.css", media_type="text/css")
@app.get("/config", response_model=ClientConfig)
async def get_client_config():
"""Serve client configuration to frontend"""