Make dev workspaces serve a working browser IDE end to end
The workspace feature shipped its routes, agent tools and docs, but the editor was never reachable: the project page had no entry point, the ppy image had no code-server binary, no certificate was ever requested for a tunnel, and both nginx and the proxy dropped what the editor needs. - Add a VS Code button to the project action row and a Workspace item to the overflow menu, gated by can_open_workspace plus a running instance (viewer_can_workspace and workspace_editor_url on ProjectDetailOut). - Install a pinned code-server in ppy.Dockerfile before USER pravda and assert it in the build smoke test, so an image that cannot run the editor no longer builds green. - Run the editor with --auth password and a per workspace 8 character pronounceable secret, minted once at the ensure_editor_password choke point and injected as PASSWORD. Keep it off WorkspaceViewOut, which the admin listing shares. - Publish the editor tunnel when a workspace is created and issue its certificate from a new WorkspaceService phase against the molohttp admin API, then notify the owner with the live URL and the password. Only pending rows are retried, so a broken host cannot burn the ACME failure rate limit. Renewal stays molohttp's job. - Forward the original Host on proxied requests and the client cookie on proxied websockets, so code-server scopes its session cookie to the public hostname and authenticates the workbench socket. - Recreate a container stuck in the created state instead of retrying docker start forever against an image it can no longer run. - Return a JSON string from WorkspaceController.dispatch; raw dicts landed in a tool message and aborted the turn at the model endpoint. - Let the nginx catch-all carry websocket upgrades, keeping upstream keepalive, so tunnelled apps and the editor both connect.
This commit is contained in:
@@ -421,6 +421,36 @@ def test_private_detail_visible_to_admin(app_server):
|
||||
)
|
||||
|
||||
|
||||
def _await_workspace_flag(url, key, expected, timeout=10.0):
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
body = requests.get(url, headers=_h_project_visibility(key)).json()
|
||||
if body["viewer_can_workspace"] is expected:
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return False
|
||||
|
||||
|
||||
def test_detail_json_exposes_viewer_can_workspace(app_server):
|
||||
_, _, owner_key = _signup_project_visibility()
|
||||
_, _, other_key = _signup_project_visibility()
|
||||
slug = _create_project_project_visibility(owner_key, "Workspace Flag")["slug"]
|
||||
url = f"{BASE_URL}/projects/{slug}"
|
||||
previous = get_table("site_settings").find_one(key="workspace_enabled")
|
||||
set_setting("workspace_enabled", "1")
|
||||
try:
|
||||
assert _await_workspace_flag(url, owner_key, True)
|
||||
other = requests.get(url, headers=_h_project_visibility(other_key)).json()
|
||||
assert other["viewer_can_workspace"] is False
|
||||
|
||||
set_setting("workspace_enabled", "0")
|
||||
assert _await_workspace_flag(url, owner_key, False)
|
||||
finally:
|
||||
set_setting(
|
||||
"workspace_enabled", previous.get("value") if previous else "0"
|
||||
)
|
||||
|
||||
|
||||
def test_project_uid_redirects_to_canonical_slug(app_server):
|
||||
slug, uid = _seed_project()
|
||||
r = requests.get(f"{BASE_URL}/projects/{uid}", allow_redirects=False)
|
||||
|
||||
Reference in New Issue
Block a user