diff --git a/DEVELOPER.md b/DEVELOPER.md index e15e0f4..e589aa5 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -909,6 +909,8 @@ Nest. Both server entry points expose the UVM management routes below. | `GET` | `/vms/{vm_id}/sessions` | `nest/session_routes.py` | Saved session list (Nest only) | | `POST` | `/vms/{vm_id}/sessions` | `nest/session_routes.py` | Launch a guest PTY; optional rows/cols in a JSON object (Nest only) | | `GET` | `/vms/{vm_id}/sessions/{session_id}` | `nest/session_routes.py` | VM-scoped session metadata (Nest only) | +| `PATCH` | `/vms/{vm_id}/sessions/{session_id}` | `nest/session_routes.py` | Rename via a strict `name` body; identity retained (Nest only) | +| `DELETE` | `/vms/{vm_id}/sessions/{session_id}` | `nest/session_routes.py` | Close channel, join output reader, delete history; 204 (Nest only) | | `POST` | `/vms/{vm_id}/sessions/{session_id}/close` | `nest/session_routes.py` | End a shell; retain history (Nest only) | | `WS` | `/vms/{vm_id}/sessions/{session_id}/terminal` | `nest/session_routes.py` | Authenticated replay/input/resize (Nest only) | @@ -1051,8 +1053,12 @@ window-size changes are handled by the guest terminal driver. Normal exit, explicit close, or host connection loss cleans up the job cgroup and processes. `SessionManager` owns background output readers independently of WebSocket -attachments. Creating a shell is shielded from an HTTP disconnect; its connection -is either tracked or closed. Browser navigation, reload, and logout detach only +attachments. Accepted creation, rename, close, and delete operations are tracked +and shielded from HTTP disconnects. Creation and termination share a session +lock, so deleting a starting session waits for its channel to be tracked and +then closes it. Deletion joins the output reader and its pending writes before +atomically removing the metadata and transcript rows. Browser navigation, +reload, and logout detach only the browser. Nest shutdown closes guest connections and marks live sessions interrupted. In-flight transcript writes finish before releasing store ownership. @@ -1062,7 +1068,11 @@ directory, mode-0600 files, an exclusive owner lock, and SQLite transactions protect history. On reopening after a server restart, unfinished records become interrupted. Session lookups always include the exact VM ID. Output is capped at the newest 8 MiB per session with byte offsets and an explicit truncation marker; -session metadata is retained. Only PTY output is recorded, not a separate input +session metadata is retained until explicit deletion. Session names are trimmed, +1–80 printable characters, and are display labels rather than identifiers. +Renaming updates only the name and modification time; generated default names +avoid labels still present after renames or deletions. Only PTY output is +recorded, not a separate input or password log. Nest permits 32 active sessions; guest terminals share the agent's eight connection slots with one-shot jobs. @@ -1074,6 +1084,9 @@ offset after xterm renders the chunk. Only one chunk is outstanding, providing backpressure. Reattachment resumes from an acknowledged offset; a truncated prefix resets the viewer. Closed sessions use the same channel for read-only replay. WebSocket transport support comes from the `websockets` dependency. +Renames notify attached viewers through updated session metadata. A deleted +session sends a `deleted` event to authenticated attachments; polling also clears +stale selections for completed sessions whose replay connection already closed. The dashboard's Sessions tab and nested hash routes belong to the selected VM. `sessions.js` owns attachment teardown, replay, reconnects, and the terminal @@ -1182,7 +1195,8 @@ NODE_PATH=/tmp/nest-browser/node_modules \ ``` This checks launch/input, reload/reconnect, session switching, per-VM isolation, -closed-session replay, and the mobile layout. It does not boot a real microVM. +closed-session replay, rename persistence, active/ended deletion, cancellation, +multi-tab updates, and the mobile layout. It does not boot a real microVM. When `nft` and `conntrack` are installed, command-parser tests append an invalid final command/option so parsing is checked without executing any mutation. diff --git a/README.md b/README.md index 95329b9..e7a657b 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,8 @@ The server exposes these routes: | `GET` | `/vms/{vm_id}/sessions` | List this VM's saved sessions (Nest) | | `POST` | `/vms/{vm_id}/sessions` | Launch a guest terminal; body: `{}` or `{"rows":24,"cols":80}` (Nest) | | `GET` | `/vms/{vm_id}/sessions/{session_id}` | Session metadata and status (Nest) | +| `PATCH` | `/vms/{vm_id}/sessions/{session_id}` | Rename a session; body: `{"name":"Deployment"}` (Nest) | +| `DELETE` | `/vms/{vm_id}/sessions/{session_id}` | Close the session and delete its saved history; returns 204 (Nest) | | `POST` | `/vms/{vm_id}/sessions/{session_id}/close` | End the shell and retain its history (Nest) | | `WS` | `/vms/{vm_id}/sessions/{session_id}/terminal` | Authenticated terminal input, output, resize, and replay (Nest) | @@ -379,6 +381,13 @@ output. Navigation, browser reloads, and signing out leave the shell running. **End session** closes the shell and its processes. Stopping/restarting the VM or the Nest server ends live sessions; their saved history remains available. +Use **Rename** in a history row or the terminal toolbar to give any session a +custom name. Names are trimmed and accept 1–80 printable characters; they persist +across reloads and server restarts. The session keeps its identity and shell. +**Delete** removes a session and its saved terminal output after confirmation, +closing its shell first if it is active. Both actions are scoped to the session's +microVM. + Nest stores session metadata and the latest **8 MiB of output per session** in `$UVM_BASE/nest/sessions.sqlite3` (default `/var/lib/uvm/nest/sessions.sqlite3`). The viewer indicates truncated output. Raw keyboard input is not logged diff --git a/USAGE.md b/USAGE.md index 4054b01..5983edb 100644 --- a/USAGE.md +++ b/USAGE.md @@ -823,6 +823,11 @@ passwords in reports. 5. Use **Session history** to switch between that VM's sessions. Active sessions reconnect to their existing shell; ended sessions replay saved output. 6. Use **End session** when finished, or run `exit` inside the shell. +7. Use **Rename** in the history row or terminal toolbar to save a custom label. + Names accept 1–80 printable characters, with surrounding whitespace trimmed. +8. Use **Delete** to permanently remove a session and its saved output. Confirming + deletion closes an active shell before removing its history. **End session** + retains history for later viewing. Sessions continue while you navigate elsewhere, reload, or sign out of the dashboard. VM shutdown/restart or Nest shutdown ends the shell. Session metadata, @@ -830,6 +835,9 @@ start/end times, exit status, and output survive Nest restarts in `$UVM_BASE/nest/sessions.sqlite3`; files are mode `0600` in a mode-`0700` directory. Only sessions belonging to the selected exact VM ID are shown. Reusing a VM name does not attach another VM's history. +Renaming works for active and ended sessions and keeps their IDs and URLs stable. +Deleting the open session clears the terminal selection; other sessions continue +to appear under their own history entries. The retained transcript is capped at its latest 8 MiB, and the terminal viewer has a 10,000-line scrollback. A truncation note appears when older output is @@ -916,6 +924,8 @@ curl --cacert /path/to/ca.pem \ | `GET` | `/vms/{vm_id}/sessions` | List saved sessions for an exact VM ID (Nest) | | `POST` | `/vms/{vm_id}/sessions` | Launch a terminal, with `{}` or optional `rows`/`cols` (Nest) | | `GET` | `/vms/{vm_id}/sessions/{session_id}` | Inspect a session (Nest) | +| `PATCH` | `/vms/{vm_id}/sessions/{session_id}` | Rename with `{"name":"Deployment"}`; returns updated metadata (Nest) | +| `DELETE` | `/vms/{vm_id}/sessions/{session_id}` | End the shell and remove its record/output; HTTP 204 (Nest) | | `POST` | `/vms/{vm_id}/sessions/{session_id}/close` | End a session and retain its output (Nest) | | `WS` | `/vms/{vm_id}/sessions/{session_id}/terminal` | Authenticated live terminal/replay (Nest) | diff --git a/nest/dashboard/static/css/sessions.css b/nest/dashboard/static/css/sessions.css index ee49b3a..c4234c0 100644 --- a/nest/dashboard/static/css/sessions.css +++ b/nest/dashboard/static/css/sessions.css @@ -10,7 +10,10 @@ .session-history > .panel-footnote { margin-top: auto; padding: 15px 8px 0; } .session-list { display: grid; gap: 6px; max-height: 510px; overflow-y: auto; } .session-list-empty { padding: 24px 9px; color: var(--muted); font-size: 10px; line-height: 1.8; } -.session-item { display: grid; grid-template-columns: 1fr auto; gap: 7px; padding: 12px 10px; border: 1px solid transparent; border-radius: 12px; color: var(--ink-soft); } +.session-item { display: grid; gap: 9px; min-width: 0; padding: 12px 10px; border: 1px solid transparent; border-radius: 12px; color: var(--ink-soft); } +.session-link { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; } +.session-item-actions { display: flex; gap: 15px; padding-top: 8px; border-top: 1px solid var(--line); } +.session-item-actions .quiet-link { min-height: 24px; font-size: 9px; } .session-item:hover { background: #fff8; } .session-item.active { background: var(--white); border-color: #d9d0e4; } .session-item strong { overflow-wrap: anywhere; font-size: 11px; } @@ -20,11 +23,14 @@ .session-state.failed, .session-state.interrupted { background: var(--peach); } .terminal-panel { min-width: 0; overflow: hidden; border-radius: 17px; background: #25222d; color: #e8e3ef; box-shadow: var(--shadow-card); } .terminal-toolbar { min-height: 56px; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-bottom: 1px solid #ffffff12; background: #302c39; } -.terminal-toolbar > div:first-child { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; } -.terminal-toolbar strong { font-size: 11px; } +.terminal-toolbar > div:first-child { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-width: 0; } +.terminal-toolbar strong { font-size: 11px; overflow-wrap: anywhere; } .terminal-status { color: #b8afc6; font-size: 9px; } .terminal-toolbar .button { color: #e8e3ef; background: #ffffff0c; border-color: #ffffff17; } .terminal-toolbar .button:hover { background: #ffffff19; } +#session-delete { color: #e5b4aa; } +.session-rename-dialog .field { margin-top: 22px; } +#session-rename-error { color: var(--red); } .terminal-lights { display: inline-flex; gap: 4px; margin-right: 3px; } .terminal-lights i { height: 6px; width: 6px; border-radius: 50%; background: var(--peach); } .terminal-lights i:nth-child(2) { background: var(--yellow); } diff --git a/nest/dashboard/static/index.html b/nest/dashboard/static/index.html index f4ca18a..39c7730 100644 --- a/nest/dashboard/static/index.html +++ b/nest/dashboard/static/index.html @@ -11,11 +11,11 @@ - + - - + +