Browser UI in the style of CK-X: landing page, exam screen, results
Rebuilt web/index.html after the layout of CK-X (itself modelled on the Linux Foundation exam UI), written from scratch and loading nothing from the network: - landing page: track switch in the top bar, a Start exam dialog to pick the session (path, objective, domain, next domain, whole bank, picked tasks, simulator sets, full exam with its gate, hard mode), the path, a card per domain, every objective with its notes and skip links, and an overlay with the clusters' progress while a session is prepared - exam screen: dark top bar with the clock and the Exam Interface / Exam Controls / Exam Information menus, a light question panel with < Question N >, "Solve this question on instance", Flag for review, Satisfied with answer, and Check this task in drills; the remote desktop with reconnect and fullscreen beside it - results page: points, pass or fail, domains, every task's checks and reference solution, answers, drill what I missed, review on the hosts A session started in the browser opens a dialog whose Start puts the full time on the clock. New POST /api/terminate backs Terminate session (an open session ends ungraded, as ./ksim abort) and Delete the hosts (a finished session loses its hosts); both delete that session's hosts only. The session payload says whether a session was aborted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
This commit is contained in:
parent
c244bd9c75
commit
bab85d14b7
31
README.md
31
README.md
@ -85,15 +85,34 @@ desktop; the VM host adds a 3.5 GB base disk and the 0.6 GB Ubuntu cloud image u
|
||||
```
|
||||
./ksim build all # once: the exam host image per Kubernetes minor, and the desktop image
|
||||
./ksim build cks --vm # once, for CKS: also the VM host its Falco and AppArmor tasks need (~10 min)
|
||||
./ksim web # http://127.0.0.1:8080 — pick a track and a session in the lobby
|
||||
./ksim web # http://127.0.0.1:8080 — pick a track and a session on the landing page
|
||||
```
|
||||
|
||||
In the browser: the task on the left names its host; click `ssh cka0037` (or any highlighted
|
||||
value) to put it on the desktop's clipboard and paste in the terminal with **Ctrl+Shift+V**.
|
||||
The browser UI follows the layout of [CK-X](https://github.com/sailor-sh/CK-X) (itself modelled on
|
||||
the Linux Foundation's exam UI), written anew for this simulator:
|
||||
|
||||
- **Landing page** — the track (CKA, CKAD, CKS) in the top bar; **Start exam** opens a dialog to
|
||||
pick the session: the next step on the path, an objective, a domain, the next domain, the whole
|
||||
bank, tasks of your choice, a simulator set or a full exam, with or without hard mode. Below it:
|
||||
your path, a card per domain, and every objective with its notes and the "I know this — skip it"
|
||||
links. While the clusters are built, an overlay shows their progress and the log.
|
||||
- **Exam screen** — a dark top bar with the clock and three menus: *Exam Interface* (fullscreen,
|
||||
panel sizes, reconnect the desktop), *Exam Controls* (submit, pause, **Restart clock**,
|
||||
**Terminate session**) and *Exam Information* (exam instructions, Kubernetes docs, your history).
|
||||
On the left, `< Question N >` and each task headed by its host: click `ssh ckad0037` (or any
|
||||
highlighted value) to put it on the desktop's clipboard and paste in the terminal with
|
||||
**Ctrl+Shift+V**. **Flag for review** and **Satisfied with answer** sit under the task; a drill
|
||||
adds **Check this task**, and a task's reference solution opens once it is graded. On the right,
|
||||
the remote desktop. The clock starts when you click **Start** in the dialog that opens the session.
|
||||
- **Results page** — points and pass or fail, every task with its checks, the answers, what to drill
|
||||
next, and a way back to the tasks to review them on their hosts.
|
||||
|
||||
Firefox in the desktop opens only the allowed documentation for the track (`KSIM_OPEN_WEB=1`
|
||||
lifts that). Submit grades every task on its own host; the hosts stay up for review until the next
|
||||
session starts. **Restart clock** in the top bar (`./ksim restart`) puts the session's full time
|
||||
back on the clock without touching its hosts or your work; like Pause, it is recorded nowhere.
|
||||
lifts that). Submitting grades every task on its own host; the hosts stay up for review until the
|
||||
next session starts, or until **Delete the hosts** on the results page. **Restart clock**
|
||||
(`./ksim restart`) puts the session's full time back on the clock without touching its hosts or
|
||||
your work; like Pause, it is recorded nowhere. **Terminate session** ends a session without grading
|
||||
it and deletes its hosts, as `./ksim abort` does.
|
||||
|
||||
Everything is also available from a terminal. A command is for the track named with `--track` or
|
||||
`KSIM_TRACK`; with neither, for the track whose session is under way, and for CKA when there is
|
||||
|
||||
19
ksim
19
ksim
@ -22,7 +22,7 @@ from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
VERSION = "2026.10.15.2"
|
||||
VERSION = "2026.10.15.3"
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, ROOT)
|
||||
from sim import desktop, envs # noqa: E402
|
||||
@ -1340,6 +1340,15 @@ def clear_hosts(keep=()):
|
||||
list(pool.map(envs.destroy, old))
|
||||
return old
|
||||
|
||||
def drop_hosts(st):
|
||||
"""Delete the hosts of one session's tasks and nothing else - the browser's Terminate session
|
||||
and Delete the hosts, which must not reach another track's session."""
|
||||
mine = {host_of(q) for q in session_questions(st)}
|
||||
gone = [h for h in envs.existing() if h in mine]
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=PARALLEL) as pool:
|
||||
list(pool.map(envs.destroy, gone))
|
||||
return gone
|
||||
|
||||
def open_sessions_elsewhere():
|
||||
"""Tracks other than this one with a session that has not ended. One session at a time: they
|
||||
share the desktop, and a new session clears the hosts of the last one."""
|
||||
@ -1862,6 +1871,12 @@ class Api(http.server.BaseHTTPRequestHandler):
|
||||
if st.get("ended_at"):
|
||||
return self._json({"error": "This session has ended - there is no clock to restart."}, 400)
|
||||
restart_clock(st)
|
||||
elif p.path == "/api/terminate": # an open session ends ungraded, as ./ksim abort; an ended one only loses its hosts
|
||||
if Api.seeding:
|
||||
return self._json({"error": "A session is being prepared - wait until it is ready, then terminate it."}, 409)
|
||||
if not st.get("ended_at"):
|
||||
st["ended_at"] = time.time(); st["aborted"] = True; save_state(st)
|
||||
drop_hosts(st)
|
||||
elif p.path == "/api/notes":
|
||||
st["notes"] = body.get("notes", ""); save_state(st)
|
||||
elif p.path == "/api/grade":
|
||||
@ -1911,7 +1926,7 @@ class Api(http.server.BaseHTTPRequestHandler):
|
||||
"over_time": st.get("over_time", False), "flags": st.get("flags", []),
|
||||
"paused": bool(st.get("paused_at")), "paused_total": paused_total(st),
|
||||
"notes": st.get("notes", ""),
|
||||
"objective": st.get("objective"), "milestones": st.get("milestones", []),
|
||||
"objective": st.get("objective"), "milestones": st.get("milestones", []), "aborted": bool(st.get("aborted")),
|
||||
"results": st["results"], "score": {"earned": earned, "total": total, "pct": pct, "pass_pct": PASS_PCT},
|
||||
"domains": self.domain_scores(st), "server_time": time.time()}
|
||||
return payload
|
||||
|
||||
1404
web/index.html
1404
web/index.html
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user