# 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)