# Shell & Logs Windows Feature Specification ## Overview Shell and Logs windows are specialized detail windows that stream real-time data from Kubernetes pods. Shell provides an interactive terminal, while Logs shows container output. --- ## Shell Window ### Purpose Interactive pseudo-terminal session inside the application, connected to a pod via `kubectl exec`. ### Implementation #### Client-Side (xterm.js) ```typescript // Initialization import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; const term = new Terminal({ rows: 24, cols: 80, fontFamily: 'IBM Plex Mono', fontSize: 12, theme: { background: '#0b0e13', foreground: '#e6edf6', cursor: '#4dd6e8' } }); const fitAddon = new FitAddon(); term.loadAddon(fitAddon); fitAddon.fit(); ``` #### WebSocket Connection ```typescript // On krate creation with shell tab const ws = new WebSocket(`/ws/shell?pod=${pod}&ns=${ns}`); ws.onopen = () => { term.focus(); }; ws.onmessage = (event) => { term.write(event.data); // stdout/stderr }; term.onData((data) => { ws.send(data); // stdin (keypresses, etc.) }); term.onResize(({ rows, cols }) => { ws.send(JSON.stringify({ type: 'resize', rows, cols })); }); ``` ### Keyboard Routing #### Mouse-Over Focus - Track `mouseenter`/`mouseleave` per window - Set `hoveredWindowId` on shell windows - Global `keydown` handler routes to shell when focused #### Critical: Space Key - **Must NOT open spotlight** when shell has focus - Shell window handles space as input (terminal command) - Implementation: ```typescript document.addEventListener('keydown', (e) => { if (hoveredWindowId && isShellWindow(hoveredWindowId)) { // Space goes to shell return; // Don't trigger canvas shortcuts } // Handle canvas shortcuts (space pan, etc.) }, { capture: true }); ``` ### Scroll Wheel Behavior - On shell content: Scroll terminal (not canvas) - On empty canvas: Pan - **Ctrl/⌘ + scroll over shell**: Zoom canvas (not terminal zoom) - Use `{ capture: true }` on canvas wheel handler - Check `e.ctrlKey || e.metaKey` - If true: zoom canvas; otherwise: let event propagate ### Terminal Features - **Full PTY emulation**: Support all ANSI escapes - **Resize**: Send new size on container resize - **Paste**: Support Ctrl+Shift+V or Shift+Insert - **Copy**: Ctrl+Shift+C or Ctrl+C (custom) - **Search**: `/` to search, `n`/`N` for next/prev - **Font scaling**: Ctrl+wheel for terminal font size ### Backend Implementation (Go) ```go // WebSocket connection func (s *Server) handleShell(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { return } defer ws.Close() pod := r.URL.Query().Get("pod") ns := r.URL.Query().Get("ns") // Create k8s exec request req := s.client.CoreV1().RESTClient().Post(). Name(pod).Namespace(ns).Resource("pods").SubResource("exec") req.VersionedParams(&v1.ExecOptions{ Stdin: true, Stdout: true, Stderr: true, Terminal: true, }, scheme.ParameterCodec) exec, err := remotecommand.NewSPDYExecutor(s.config, "POST", req.URL()) if err != nil { ws.Close() return } // Stream between WebSocket and exec exec.Stream(remotecommand.StreamOptions{ Stdin: ws, Stdout: ws, Stderr: ws, TermSize: &remotecommand.TerminalSize{ Width: 80, Height: 24, }, }) } ``` --- ## Logs Window ### Purpose Live-tailing container logs, showing output in real-time. ### Streaming ```typescript // WebSocket connection const ws = new WebSocket(`/ws/logs?pod=${pod}&ns=${ns}`); ws.onmessage = (event) => { const line = event.data; addLogLine(line); }; function addLogLine(line: string) { const entry = parseLogLine(line); logBuffer.push(entry); // Auto-scroll if at bottom if (atBottom()) { scrollToBottom(); } } ``` ### Auto-Scroll Behavior - **New lines arrive**: Auto-scroll to bottom - **User scrolls up**: Stop auto-scrolling - **Detection**: ```typescript function atBottom(): boolean { const threshold = 50; // px return (scrollTop + clientHeight + threshold) >= scrollHeight; } ``` ### Color Coding Parse lines and color-code by pattern: ```typescript function parseLogLine(line: string): LogEntry { // Pattern matching for common log levels if (line.includes('ERROR') || line.includes('ERR')) { return { text: line, color: '#ef6f6f' }; // Red } else if (line.includes('WARN') || line.includes('WARNING')) { return { text: line, color: '#e8b54a' }; // Amber } else if (line.includes('INFO') || line.includes('I ')) { return { text: line, color: '#b9c6d8' }; // Default } else { return { text: line, color: '#c7d2e0' }; // Muted } } ``` ### Timestamps - **Default**: Include timestamps - **Toggle**: Optional button to show/hide - **Format**: ISO 8601 or container format ### Scrollbar Styling - Width: 8px - Track: rgba(140,165,200,.1) - Thumb: rgba(140,165,200,.3) - Hover thumb: rgba(140,165,200,.5) ### Content Rendering ```typescript
  {logs.map((line, i) => (
    
{line.timestamp && {line.timestamp} } {line.text}
))}
``` --- ## View-Specific Behavior ### Shell Tab - **Enabled for**: Pods, and workloads that support exec - **Disabled for**: Deployments, Services, etc. - **Visual**: `opacity: 0.45` for disabled - **Label**: Show "shell" but don't route to it ### Logs Tab - **Enabled for**: Pods and workloads ( Deployments, StatefulSets, DaemonSets) - **Disabled for**: ConfigMaps, Secrets, PVCs, etc. - **Visual**: Same as shell --- ## Keyboard Shortcuts | Key | Context | Action | |---|---|---| | ↑ / ↓ | Logs shell focused | Navigate scroll (logs) / line history (shell) | | PageUp / PageDown | Logs focused | Page scroll | | Ctrl+L | Logs focused | Clear screen (only in shell) | | Ctrl+C | Shell focused | Send interrupt to shell | | Ctrl+Z | Shell focused | Send suspend (if supported) | | Ctrl+Shift+F | Logs focused | Find (future enhancement) | | Ctrl+Shift+V | Shell focused | Paste | | Ctrl+Shift+C | Shell focused | Copy selection | --- ## Error Handling ### Connection Errors - **Shell**: Show error border, keep terminal buffer, retry button - **Logs**: Show error indicator, auto-reconnect with backoff ### Reconnection Strategy ```typescript let retries = 0; const maxRetries = 5; function reconnect() { if (retries >= maxRetries) { showFatalError('Connection failed'); return; } setTimeout(() => { retries++; connect(); }, Math.min(1000 * 2^retries, 10000)); // Exponential backoff } ``` ### Buffer Management - **Shell**: Keep last 1000 lines in buffer - **Logs**: Keep last 2000 lines - **Overflow**: Drop oldest lines, mark with "..." indicator --- ## State Management ### Shell State ```typescript interface ShellState { connected: boolean; rows: number; cols: number; buffer: string[]; // Last N lines cursor: { x, y }; } ``` ### Logs State ```typescript interface LogsState { lines: LogEntry[]; scrollTop: number; autoScroll: boolean; timestamp: boolean; } ``` --- ## Gotchas ### Critical Issues 1. **Space key routing**: Must distinguish between shell space (input) and canvas space-pan 2. **Ctrl/⌘+scroll**: Intercept on canvas with `{ capture: true }`, check modifier 3. **Terminal resize**: Send `TermSize` on window resize events 4. **Paste security**: Sanitize pasted content, avoid escape sequences 5. **Cursor position**: xterm.js tracks cursor; keep in sync with server 6. **Buffer overflow**: Implement LRU buffer, don't memory leak ### Performance 1. **Throttle updates**: Don't render on every log line, batch 2. **Virtual scrolling**: For logs with many lines 3. **Web Worker**: Offload parsing to worker for large logs ### Animation 1. **Blinking cursor**: CSS animation or xterm.js built-in 2. **Auto-scroll indicator**: Small arrow when scrolled up 3. **Connection status**: Green/amber/red dot ## Testing Checklist - [ ] Shell key press → terminal receives it - [ ] Terminal output → WebSocket → UI - [ ] Resize → terminal resizes correctly - [ ] Space in shell ≠ spotlight open - [ ] Ctrl+scroll over shell ≠ zoom canvas - [ ] Logs auto-scroll works (and stops on manual scroll) - [ ] Connection loss → retry with backoff - [ ] Buffer overflow → old lines dropped - [ ] Shell interrupt (Ctrl+C) handled - [ ] Paste works (Ctrl+Shift+V)