# Optional sections Three extras live in `dashboard.html` as marked blocks (`OPTIONAL: BEGIN` ... `END`) in the style, the markup and the script. Build only the ones the user picked: ``` python /templates/sections.py /dashboard/dashboard.html checkpoints admin login ``` Name only the chosen sections; name none to remove all three. The script deletes every unchosen block (markup, script and style) and the marker lines of the chosen ones, and it stops if a marker is broken. Never cut the blocks by hand. With `SAMPLE_DATA = true`, every section works on its own sample answers. Once the bot is wired, the host must answer the section's actions as below. Desktop hosts add the slots to the `Bridge` class in `bridge.py`. Web hosts serve `POST {API_BASE}/`, like the core actions, and send the events in the `/run/status` events list. ## Checkpoint tracker (`checkpoints`) Numbered boxes on the Run tab. Each one is pending, in progress, done or stuck. - Fill `{{CHECKPOINTS_JSON}}` with the checkpoint names, in order, from the bot's real stages, for example `["Open the portal","Load the list","Process items","Finish up"]`. They can be finer than `STEPS`. - Event `cpstate {kind: "pending"|"active"|"done"|"stuck", cp}`. `cp` counts from 1. `active` also marks the earlier boxes done and the later ones pending, so the bot can send `active` for each checkpoint as it starts, and `stuck` when one fails. - Desktop: add a marker table next to `STEP_MARKERS` in `bot_runner.py`, and add the event in `events_for`, using specific substrings the bot really prints. The dry run then shows the `cpstate` events too: ```python # (specific substring, kind, cp). First match wins. CHECKPOINT_MARKERS = [("[cp 1]", "active", 1), ("[cp 1 failed]", "stuck", 1)] # in events_for, right after the step marker: if cp := _first(CHECKPOINT_MARKERS, line): out.append(("cpstate", {"kind": cp[1], "cp": cp[2]})) ``` ## Admin/roles screen (`admin`) An Admin tab where an admin assigns or removes admin and operator access. The Admin nav item and the screen appear only while `set_user` says `role: "admin"`; operators never see them. Hiding them is not security: **the host re-checks that the signed-in user is an admin on every admin action**, and refuses otherwise. - Event `admin_data {rows: [{username, emp_id, role: "admin"|"operator"}], msg?}`. `msg` is shown above the list, for example "Access assigned." or "Only admins can do that." - Actions: `load_admin()` when the tab opens, `assign_role(who, role)`, `remove_role(who)`. `who` is a username or employee ID. Answer each one with a fresh `admin_data`. - Desktop slots, using the project's own user store: ```python @pyqtSlot() def load_admin(self): if self.role != "admin": # self.role: set when the user signs in return self.send_to_js("admin_data", {"rows": read_access_list()}) @pyqtSlot(str, str) def assign_role(self, who, role): if self.role == "admin" and role in ("admin", "operator"): save_access(who, role) self.load_admin() @pyqtSlot(str) def remove_role(self, who): if self.role == "admin": delete_access(who) self.load_admin() ``` ## Login page (`login`) A sign-in screen in the page's own theme, shown over the dashboard until sign-in succeeds, plus a Sign out button in the top bar. It is UI only: it hands the username and password to the project's existing auth. Don't build an auth backend. - Desktop action `login(username, password)`. The host checks them and sends `login_result {ok, msg?, user?: {username, emp_id, role}}`. On `ok` the screen closes and `user` fills the sidebar (and the role, if the admin screen is built). Otherwise `msg` shows under the form. - Web: `POST {API_BASE}/login` with JSON `{username, password}`, answered with the same `{ok, msg?, user?}` body. - Action `logout()` when the user signs out. The screen comes back. - The page sits behind the screen, but the host must still refuse data actions until sign-in succeeds. - No auth in the project yet: keep a stub that lets any non-empty username in, and tell the user it is a stub. In sample mode the page has its own stub. ```python @pyqtSlot(str, str) def login(self, username, password): role = check_login(username, password) # the project's auth: a role, or None self.role = role or "" if role: self.send_to_js("login_result", {"ok": True, "user": {"username": username, "emp_id": "", "role": role}}) else: self.send_to_js("login_result", {"ok": False, "msg": "Wrong username or password."}) @pyqtSlot() def logout(self): self.role = "" ```