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.