Compare commits
No commits in common. "fe86b4be35a7ae7c2a172c9a76b2d48f19f4bbfd" and "accb26159ef5b40ccbe00b0035d5d68902d08a91" have entirely different histories.
fe86b4be35
...
accb26159e
12
CHANGELOG.md
12
CHANGELOG.md
@ -4,18 +4,6 @@ Notable changes to kubestronaut-sim, newest first. A version is the `VERSION` st
|
|||||||
(year.month.day.n); `./ksim help` and the top bar of the web page show the one you are running.
|
(year.month.day.n); `./ksim help` and the top bar of the web page show the one you are running.
|
||||||
Every entry names the commit it came in.
|
Every entry names the commit it came in.
|
||||||
|
|
||||||
## 2026.10.26.3 — 2026-10-26
|
|
||||||
|
|
||||||
Everything below came in `95f9d1b`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Choosing *Teach a domain* could start a full exam. `./ksim web` reads `web/index.html` afresh on
|
|
||||||
every request but keeps running the code it started with, so a server from before the update
|
|
||||||
read the new request as its default, a full exam. The server now reports when its code has
|
|
||||||
changed on disk since it started (a server from before this check counts as changed). The
|
|
||||||
lobby then asks you to restart it and starts nothing until you do.
|
|
||||||
|
|
||||||
## 2026.10.26.2 — 2026-10-26
|
## 2026.10.26.2 — 2026-10-26
|
||||||
|
|
||||||
Everything below came in `e852419`.
|
Everything below came in `e852419`.
|
||||||
|
|||||||
15
ksim
15
ksim
@ -22,7 +22,7 @@ from collections.abc import Callable
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
VERSION = "2026.10.26.3"
|
VERSION = "2026.10.26.2"
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, ROOT)
|
sys.path.insert(0, ROOT)
|
||||||
from sim import desktop, envs # noqa: E402
|
from sim import desktop, envs # noqa: E402
|
||||||
@ -2165,17 +2165,9 @@ def cmd_reset(args):
|
|||||||
print(("rebuilt " if not problem else "problem rebuilding ") + f"Q{q['id']:02d}: {q['title']}")
|
print(("rebuilt " if not problem else "problem rebuilding ") + f"Q{q['id']:02d}: {q['title']}")
|
||||||
|
|
||||||
# --------------------------------------------------------------- web ----
|
# --------------------------------------------------------------- web ----
|
||||||
def code_stamp():
|
|
||||||
"""When the simulator's own code (ksim, sim/*.py) last changed on disk."""
|
|
||||||
sim_dir = os.path.join(ROOT, "sim")
|
|
||||||
return max(os.path.getmtime(f) for f in [os.path.join(ROOT, "ksim")]
|
|
||||||
+ [os.path.join(sim_dir, n) for n in os.listdir(sim_dir) if n.endswith(".py")])
|
|
||||||
|
|
||||||
|
|
||||||
class Api(http.server.BaseHTTPRequestHandler):
|
class Api(http.server.BaseHTTPRequestHandler):
|
||||||
"""Serves web/index.html plus a small JSON API. State lives in the same files the CLI uses."""
|
"""Serves web/index.html plus a small JSON API. State lives in the same files the CLI uses."""
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
started_code = None # code_stamp() when ./ksim web started
|
|
||||||
log_lines = []
|
log_lines = []
|
||||||
seeding = False
|
seeding = False
|
||||||
seed_error = None
|
seed_error = None
|
||||||
@ -2353,10 +2345,6 @@ class Api(http.server.BaseHTTPRequestHandler):
|
|||||||
"k8s": k8s_version(), "pending": len(PENDING), "drilled": drilled_domains(), "sets": {k: {"name": v, "count": len(set_questions(k))} for k, v in SETS.items()},
|
"k8s": k8s_version(), "pending": len(PENDING), "drilled": drilled_domains(), "sets": {k: {"name": v, "count": len(set_questions(k))} for k, v in SETS.items()},
|
||||||
"seen": sorted(last_seen()),
|
"seen": sorted(last_seen()),
|
||||||
"seeding": Api.seeding, "seed_error": Api.seed_error,
|
"seeding": Api.seeding, "seed_error": Api.seed_error,
|
||||||
# index.html is read from disk on every request, the server's code only when it started:
|
|
||||||
# after an update the page may ask for what this server does not know (an old one read
|
|
||||||
# "teach a domain" as a full exam), so the page starts nothing until it is restarted
|
|
||||||
"stale": Api.started_code is not None and code_stamp() > Api.started_code,
|
|
||||||
"session": None,
|
"session": None,
|
||||||
}
|
}
|
||||||
payload.update(self.path_payload())
|
payload.update(self.path_payload())
|
||||||
@ -2456,7 +2444,6 @@ def cmd_web(args):
|
|||||||
except envs.EnvError as e:
|
except envs.EnvError as e:
|
||||||
Api.desktop_error = str(e)
|
Api.desktop_error = str(e)
|
||||||
print(yellow(f" no desktop: {e}"))
|
print(yellow(f" no desktop: {e}"))
|
||||||
Api.started_code = code_stamp()
|
|
||||||
http.server.ThreadingHTTPServer.allow_reuse_address = True
|
http.server.ThreadingHTTPServer.allow_reuse_address = True
|
||||||
srv = http.server.ThreadingHTTPServer(("127.0.0.1", port), Api) # type: ignore[arg-type]
|
srv = http.server.ThreadingHTTPServer(("127.0.0.1", port), Api) # type: ignore[arg-type]
|
||||||
print(bold(f"\n {TRACK_ID.upper()} exam UI: http://127.0.0.1:{port} (ksim {VERSION})\n")
|
print(bold(f"\n {TRACK_ID.upper()} exam UI: http://127.0.0.1:{port} (ksim {VERSION})\n")
|
||||||
|
|||||||
@ -397,7 +397,6 @@ try { const t = localStorage.getItem('ksim.theme'); if (t === 'light' || t === '
|
|||||||
<div class="scrollhint">SCROLL FOR YOUR PATH AND OBJECTIVES</div>
|
<div class="scrollhint">SCROLL FOR YOUR PATH AND OBJECTIVES</div>
|
||||||
</section>
|
</section>
|
||||||
<main class="wrap">
|
<main class="wrap">
|
||||||
<div class="alert alert-warning hidden" id="staleNote"></div>
|
|
||||||
<div class="alert alert-danger hidden" id="lobbyErr"></div>
|
<div class="alert alert-danger hidden" id="lobbyErr"></div>
|
||||||
<div class="alert alert-info hidden" id="lobbyNote"></div>
|
<div class="alert alert-info hidden" id="lobbyNote"></div>
|
||||||
<section class="card" id="pathCard">
|
<section class="card" id="pathCard">
|
||||||
@ -597,9 +596,6 @@ try { const t = localStorage.getItem('ksim.theme'); if (t === 'light' || t === '
|
|||||||
const fmtHMS = s => { s = Math.max(0, Math.floor(s)); return [s / 3600, s % 3600 / 60, s % 60].map(pad).join(':'); };
|
const fmtHMS = s => { s = Math.max(0, Math.floor(s)); return [s / 3600, s % 3600 / 60, s % 60].map(pad).join(':'); };
|
||||||
const esc = s => String(s).replace(/[&<>"]/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"'}[c]));
|
const esc = s => String(s).replace(/[&<>"]/g, c => ({'&': '&', '<': '<', '>': '>', '"': '"'}[c]));
|
||||||
const oldServer = what => `This ./ksim web was started before "${what}" existed. Stop it (Ctrl-C) and start it again.`;
|
const oldServer = what => `This ./ksim web was started before "${what}" existed. Stop it (Ctrl-C) and start it again.`;
|
||||||
// a server that sends no "stale" predates the check, so it is older than this page as well
|
|
||||||
const STALE = 'The simulator was updated after this ./ksim web started, and the server still runs the old code: it would misread what this page asks for (an old one starts a full exam instead of a domain\'s lessons). Stop it (Ctrl-C), start it again, and reload this page.';
|
|
||||||
const stale = () => S && S.stale !== false;
|
|
||||||
const openModal = id => $(id).classList.remove('hidden');
|
const openModal = id => $(id).classList.remove('hidden');
|
||||||
const closeModal = id => $(id).classList.add('hidden');
|
const closeModal = id => $(id).classList.add('hidden');
|
||||||
const live = () => !!(S && S.session && !S.session.ended_at);
|
const live = () => !!(S && S.session && !S.session.ended_at);
|
||||||
@ -687,7 +683,6 @@ try { const t = localStorage.getItem('ksim.theme'); if (t === 'light' || t === '
|
|||||||
$$('#trackPick [data-track]').forEach(el => el.onclick = () => switchTrack(el.dataset.track));
|
$$('#trackPick [data-track]').forEach(el => el.onclick = () => switchTrack(el.dataset.track));
|
||||||
$('#ver').textContent = 'ksim ' + (S.version || '?') + (S.pending ? ` · ${S.pending} task(s) held back` : '');
|
$('#ver').textContent = 'ksim ' + (S.version || '?') + (S.pending ? ` · ${S.pending} task(s) held back` : '');
|
||||||
$('#foot').textContent = `ksim ${S.version || '?'} · Apache-2.0 · not affiliated with the CNCF or the Linux Foundation`;
|
$('#foot').textContent = `ksim ${S.version || '?'} · Apache-2.0 · not affiliated with the CNCF or the Linux Foundation`;
|
||||||
$('#staleNote').textContent = STALE; $('#staleNote').classList.toggle('hidden', !stale());
|
|
||||||
$('#heroTitle').textContent = T.name;
|
$('#heroTitle').textContent = T.name;
|
||||||
$('#heroSub').textContent = `Kubernetes ${T.k8s}. Every task runs on its own cluster that you reach with ssh from a remote desktop, and every session runs against the clock.`;
|
$('#heroSub').textContent = `Kubernetes ${T.k8s}. Every task runs on its own cluster that you reach with ssh from a remote desktop, and every session runs against the clock.`;
|
||||||
$('#hardHero').checked = hard;
|
$('#hardHero').checked = hard;
|
||||||
@ -896,7 +891,6 @@ try { const t = localStorage.getItem('ksim.theme'); if (t === 'light' || t === '
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function launch(body) {
|
async function launch(body) {
|
||||||
if (stale()) { closeModal('#mPicker'); return lobbyError(STALE); }
|
|
||||||
closeModal('#mPicker'); $('#lobbyErr').classList.add('hidden'); $('#lobbyNote').classList.add('hidden');
|
closeModal('#mPicker'); $('#lobbyErr').classList.add('hidden'); $('#lobbyNote').classList.add('hidden');
|
||||||
$('#prepMsg').textContent = 'starting…'; $('#prepLog').textContent = ''; openModal('#mPrep'); polling = true;
|
$('#prepMsg').textContent = 'starting…'; $('#prepLog').textContent = ''; openModal('#mPrep'); polling = true;
|
||||||
try { await api('/api/start', body); await waitSeeded(); ready(true); }
|
try { await api('/api/start', body); await waitSeeded(); ready(true); }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user