diff --git a/README.md b/README.md index 5f2c926..9e07567 100644 --- a/README.md +++ b/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 diff --git a/ksim b/ksim index 175ef46..a37fe5e 100755 --- a/ksim +++ b/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 diff --git a/web/index.html b/web/index.html index 60b87f0..7d3a6ba 100644 --- a/web/index.html +++ b/web/index.html @@ -5,293 +5,510 @@ Kubestronaut exam simulator -
- Exam simulator - - - --:--:-- - - - - -
-
- -
-
-
-

- -
-
-
- - - -
-
- - - - - -
- + +
+ +
+

Kubernetes exam simulator

+

+ + +
+
SCROLL FOR YOUR PATH AND OBJECTIVES
- -
-
- - -
-
-

Exam simulator

-
-
-
-
Drill a domain (✓ = drilled at least once, » = marked known):
-
- - -
- - - - - - - -
- - - + + + - -