- dashboard.html: each section sits in OPTIONAL:<name> BEGIN/END blocks in the style, markup and script, with sample answers so a page with no bot shows it working. Core change: setUser records the role on <body>, so CSS hides admin-only parts from operators. - templates/sections.py keeps the chosen sections and deletes the rest, markers included; it stops on a broken marker. - templates/sections.md: what each section needs from the host (events, actions, desktop slot snippets, the role re-check, the login hand-off). - SKILL.md step 5 asks one multi-select question and runs sections.py. - Page check: checkpoint states, admin visibility by role and assign/remove, login theme and hand-off to auth or the stub. Cases skip on a generated page without the section; the template must carry all three. The harness signs in when the page has a login screen. - tests/test_sections.py: no sections leave no trace, and that page still passes the page check. - Eval runner: a-empty-sections variant (all three) and per-section checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
4.6 KiB
Markdown
107 lines
4.6 KiB
Markdown
# 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 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 `bridge.py`, and send the event
|
|
from `_on_line`, using specific substrings the bot really prints:
|
|
|
|
```python
|
|
CHECKPOINT_MARKERS = [("[cp 1]", "active", 1), ("[cp 1 failed]", "stuck", 1)] # (substring, kind, cp)
|
|
|
|
# in _on_line, after the step markers:
|
|
for marker, kind, cp in CHECKPOINT_MARKERS:
|
|
if marker in low:
|
|
self.send_to_js("cpstate", {"kind": kind, "cp": cp})
|
|
break
|
|
```
|
|
|
|
## 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 = ""
|
|
```
|