Files

4.7 KiB

Optional sections

Three extras live in dashboard.html as marked blocks (OPTIONAL:<name> BEGIN ... END) in the style, the markup and the script. Build only the ones the user picked:

python <skill>/templates/sections.py <project>/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}/<action-with-dashes>, like the core actions, and send the events in the /run/status events list.

Checkpoint tracker (checkpoints)

Numbered boxes in the run panel. 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:

    # (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:

    @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.

    @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 = ""