Architecture
============
Technology stack
----------------
The GUI is built on top of three libraries:
- **Flask** — WSGI web framework, handles routing, sessions, and template rendering.
- **Jinja2** — templating engine (included with Flask); templates live in ``gui/templates/``.
- **HTMX** — JavaScript library loaded from CDN; drives partial page updates and SSE
subscriptions without writing custom fetch/XHR code.
All application logic lives in ``gui/app.py``. No core files are modified.
Server-Sent Events (SSE)
-------------------------
Two independent SSE streams deliver real-time data to the browser.
Runner stream
~~~~~~~~~~~~~
When ``fwtester.py`` completes each test, it commits the result to the SQLite
database (``fwtester-sqlite3.db``, table ``tests_results``). The endpoint
``/runner/db-stream`` polls the database for new rows and emits each one as an
HTML fragment (a ``
`` element). HTMX appends each fragment to the results
table body using ``hx-swap="beforeend"``.
A second endpoint, ``/runner/stream``, tails ``outputs/logs/fwtester.log`` and
forwards each line as an SSE message. This is used by the log viewer modal
inside the Runner page.
Hot-test stream
~~~~~~~~~~~~~~~
``/hot-test/stream`` reads the standard output of the ``run_hot_test.py``
subprocess line by line and forwards each line as an SSE message. When the
subprocess exits, the generator emits an ``event: close`` message so the
browser-side ``EventSource`` can call ``src.close()`` and stop reconnecting.
Key files
---------
.. list-table::
:widths: 35 65
:header-rows: 1
* - File
- Purpose
* - ``gui/app.py``
- Flask application: all routes, SSE generators, helper functions.
* - ``gui/run_hot_test.py``
- ``HotTestRunner`` subclass of ``FwTester``; single-test runner used by hot-test.
* - ``gui/i18n.py``
- Translation dictionary and ``t(key, locale)`` helper.
* - ``gui/auth.py``
- User authentication: SQLite storage, PBKDF2-SHA256 hashing, session management.
* - ``gui/install-gui-service.sh``
- Portable systemd service installer.
* - ``gui/uninstall-gui-service.sh``
- Removes the systemd service.
* - ``gui/templates/``
- Jinja2 templates: one per page plus HTMX partials (``_*.html``).
* - ``gui/static/style.css``
- Application stylesheet (no external CSS framework).
* - ``requirements-gui.txt``
- GUI-specific Python dependencies (Flask).
HotTestRunner
-------------
``HotTestRunner`` (``gui/run_hot_test.py``) is a subclass of ``FwTester`` with
three overrides:
- ``__init__`` — skips loading ``tests.csv``; sets ``self._tests`` to a single
constructed CSV rule string built from the CLI arguments.
- ``_resume()`` — always returns ``-1`` so the single test is never skipped by
the resume logic.
- ``generate_report()`` — no-op; hot-test results are not written to the report
CSV.
It uses ``fwtester_hot-sqlite3.db`` as its database to avoid interfering with
the main runner's resume state. ``fwnics.csv`` and ``routes.csv`` must still be
present.
Internationalisation (i18n)
----------------------------
All user-visible strings are looked up through the ``t(key, locale)`` function
defined in ``gui/i18n.py``. The function resolves:
1. The string for the requested locale.
2. Falls back to the English (``en``) string if the key is missing for that locale.
3. Falls back to the key name itself if the key is not found in any locale.
The active locale is stored in the Flask session and changed via
``GET /set-lang?lang=``. Supported codes: ``en``, ``es``, ``ca``.
.. note::
Because the server runs with ``debug=False``, Python modules are loaded once
at startup and are not reloaded between requests. Changes to ``gui/i18n.py``
require a service restart to take effect.
Authentication
--------------
``gui/auth.py`` manages user accounts in the ``users`` table of
``fwtester-sqlite3.db``. Passwords are stored as PBKDF2-SHA256 hashes using
Python's ``hashlib.pbkdf2_hmac``. Sessions are handled by Flask's built-in
signed cookie mechanism.
The ``users`` table coexists with the ``tests_results`` table in the same
database file. The "Clear results" operation issues a ``DELETE FROM
tests_results`` statement and does not touch the ``users`` table.