Add SSH connection information to web UI
This commit implements:
1. Added SSH information display to the web UI info button
2. Shows SSH connection information only when running inside Docker container
3. Constructs the full SSH hostname as "sketch-[session_id]"
4. Added copy-to-clipboard buttons for SSH commands
5. Styles the VSCode URL as a proper clickable link
6. Shows a warning message when SSH is not available
7. Modified container naming to use only SessionID instead of imgName+SessionID
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/webui/src/fixtures/dummy.ts b/webui/src/fixtures/dummy.ts
index d96e873..4a40337 100644
--- a/webui/src/fixtures/dummy.ts
+++ b/webui/src/fixtures/dummy.ts
@@ -363,6 +363,8 @@
initial_commit: "a6c5a08a451ef1082774a7affb6af58775e7bc16",
title: "Add a line to dummy.txt and commit the change",
hostname: "MacBook-Pro-9.local",
+ session_id: "dummy-session",
+ ssh_available: false,
working_dir: "/Users/pokey/src/spaghetti",
os: "darwin",
git_origin: "git@github.com:pokey/spaghetti.git",
diff --git a/webui/src/types.ts b/webui/src/types.ts
index 3b672b2..25be7f7 100644
--- a/webui/src/types.ts
+++ b/webui/src/types.ts
@@ -76,6 +76,9 @@
git_origin?: string;
outstanding_llm_calls: number;
outstanding_tool_calls: string[];
+ session_id: string;
+ ssh_available: boolean;
+ ssh_error?: string;
}
export type CodingAgentMessageType = 'user' | 'agent' | 'error' | 'budget' | 'tool' | 'commit' | 'auto';
diff --git a/webui/src/web-components/sketch-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index bb05a03..9682daf 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -218,6 +218,8 @@
initial_commit: "",
outstanding_llm_calls: 0,
outstanding_tool_calls: [],
+ session_id: "",
+ ssh_available: false,
};
// Mutation observer to detect when new messages are added
diff --git a/webui/src/web-components/sketch-container-status.test.ts b/webui/src/web-components/sketch-container-status.test.ts
index b5e625d..db43a6d 100644
--- a/webui/src/web-components/sketch-container-status.test.ts
+++ b/webui/src/web-components/sketch-container-status.test.ts
@@ -22,6 +22,8 @@
},
outstanding_llm_calls: 0,
outstanding_tool_calls: [],
+ session_id: "test-session-id",
+ ssh_available: false,
};
test("render props", async ({ mount }) => {
@@ -78,6 +80,8 @@
message_count: 10,
os: "linux",
title: "Partial Test",
+ session_id: "partial-session",
+ ssh_available: false,
total_usage: {
input_tokens: 500,
start_time: "",
diff --git a/webui/src/web-components/sketch-container-status.ts b/webui/src/web-components/sketch-container-status.ts
index 08ac605..f4c0f25 100644
--- a/webui/src/web-components/sketch-container-status.ts
+++ b/webui/src/web-components/sketch-container-status.ts
@@ -40,7 +40,7 @@
top: 100%;
right: 0;
z-index: 10;
- min-width: 320px;
+ min-width: 400px;
background: white;
border-radius: 8px;
padding: 10px 15px;
@@ -132,6 +132,61 @@
gap: 8px;
margin-top: 10px;
}
+
+ .ssh-section {
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid #eee;
+ }
+
+ .ssh-command {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ gap: 10px;
+ }
+
+ .ssh-command-text {
+ font-family: monospace;
+ font-size: 12px;
+ background: #f5f5f5;
+ padding: 4px 8px;
+ border-radius: 4px;
+ border: 1px solid #e0e0e0;
+ flex-grow: 1;
+ }
+
+ .copy-button {
+ background: #f0f0f0;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 3px 6px;
+ font-size: 11px;
+ cursor: pointer;
+ transition: all 0.2s;
+ }
+
+ .copy-button:hover {
+ background: #e0e0e0;
+ }
+
+ .ssh-warning {
+ background: #fff3e0;
+ border-left: 3px solid #ff9800;
+ padding: 8px 12px;
+ margin-top: 8px;
+ font-size: 12px;
+ color: #e65100;
+ }
+
+ .vscode-link {
+ color: #2962ff;
+ text-decoration: none;
+ }
+
+ .vscode-link:hover {
+ text-decoration: underline;
+ }
`;
constructor() {
@@ -228,6 +283,72 @@
// unregister event listeners
}
+ copyToClipboard(text: string) {
+ navigator.clipboard
+ .writeText(text)
+ .then(() => {
+ // Could add a temporary success indicator here
+ })
+ .catch((err) => {
+ console.error("Could not copy text: ", err);
+ });
+ }
+
+ getSSHHostname() {
+ return `sketch-${this.state?.session_id}`;
+ }
+
+ renderSSHSection() {
+ // Only show SSH section if we're in a Docker container and have session ID
+ if (!this.state?.session_id) {
+ return html``;
+ }
+
+ const sshHost = this.getSSHHostname();
+ const sshCommand = `ssh ${sshHost}`;
+ const vscodeCommand = `code --remote ssh-remote+root@${sshHost} /app -n`;
+ const vscodeURL = `vscode://vscode-remote/ssh-remote+root@${sshHost}/app?windowId=_blank`;
+
+ if (!this.state?.ssh_available) {
+ return html`
+ <div class="ssh-section">
+ <h3>SSH Connection</h3>
+ <div class="ssh-warning">
+ SSH connections are not available:
+ ${this.state?.ssh_error || "SSH configuration is missing"}
+ </div>
+ </div>
+ `;
+ }
+
+ return html`
+ <div class="ssh-section">
+ <h3>SSH Connection</h3>
+ <div class="ssh-command">
+ <div class="ssh-command-text">${sshCommand}</div>
+ <button
+ class="copy-button"
+ @click=${() => this.copyToClipboard(sshCommand)}
+ >
+ Copy
+ </button>
+ </div>
+ <div class="ssh-command">
+ <div class="ssh-command-text">${vscodeCommand}</div>
+ <button
+ class="copy-button"
+ @click=${() => this.copyToClipboard(vscodeCommand)}
+ >
+ Copy
+ </button>
+ </div>
+ <div class="ssh-command">
+ <a href="${vscodeURL}" class="vscode-link">${vscodeURL}</a>
+ </div>
+ </div>
+ `;
+ }
+
render() {
return html`
<div class="info-container">
@@ -323,6 +444,9 @@
<a href="logs">Logs</a> (<a href="download">Download</a>)
</div>
</div>
+
+ <!-- SSH Connection Information -->
+ ${this.renderSSHSection()}
</div>
</div>
`;