docs: Finalize all design documents

Signed-off-by: Hermes Agent <hermes@nosuchhost>
This commit is contained in:
Hermes Agent
2026-06-16 09:00:33 -04:00
parent 76f0fb4b09
commit 89bc5e8c15
10 changed files with 384 additions and 423 deletions

View File

@@ -1,337 +1,232 @@
# Shell & Logs Windows Feature Specification
# Design Document: Shell & Logs 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 and Logs are real-time terminal and streaming views for Kubernetes resources. These features enable users to interact with running pods and monitor application behavior.
---
## Shell Terminal (kubectl exec)
## Shell Window
### Requirements
- **Implementation**: xterm.js on client
- **Backend**: WebSocket → kubectl exec
- **Protocol**: Bidirectional terminal protocol
### Purpose
Interactive pseudo-terminal session inside the application, connected to a pod via `kubectl exec`.
### Implementation
#### Client-Side (xterm.js)
### Terminal Configuration
```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,
lineHeight: 1.5,
cursorBlink: true,
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();
background: '#1a1e26',
foreground: '#b9c6d8',
}
}
```
### 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;
}
```
### Connection Flow
1. User opens shell view
2. Extract pod name and namespace
3. Connect WebSocket to `/ws/shell?pod=X&ns=Y`
4. Backend:
- Extract query params
- Connect to k8s exec API
- Create execstream
- Proxy bidirectionally
5. Frontend:
- Initialize xterm
- Attach to WebSocket
- Handle resize events
- Route keypresses
### Color Coding
Parse lines and color-code by pattern:
### Key Routing
- **Shell focused + type**: Route to terminal
- **Canvas shortcuts disabled** when shell has focus
- **Space key**: Goes to terminal (not spotlight)
- **Ctrl/⌘+scroll**: Zoom canvas (not terminal zoom)
```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
}
}
```
### Resize Handling
- **Client**: term.resize(cols, rows)
- **Server**: Send resize message via WebSocket
- **Backend**: Update k8s exec term size
- **Debounce**: 100ms for resize events
### 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
<pre style={{ overflow: 'auto', height: '286px', fontFamily: 'IBM Plex Mono' }}>
{logs.map((line, i) => (
<div key={i} style={{ color: line.color }}>
{line.timestamp && <span className="timestamp">{line.timestamp} </span>}
{line.text}
</div>
))}
</pre>
```
---
## 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
### State Management
```typescript
interface ShellState {
connected: boolean;
rows: number;
connectedAt: number;
lastActive: number;
buffer: string[];
cols: number;
buffer: string[]; // Last N lines
cursor: { x, y };
rows: number;
}
```
### Logs State
### Error States
- Connection failed: Show error message
- Disconnected: Show "Disconnected" + reconnect button
- Permission denied: Show clear error
- Pod not found: Show error message
## Logs Viewer (kubectl logs)
### Requirements
- **Implementation**: WebSocket streaming
- **Auto-scroll**: To bottom by default
- **Color coding**: ERROR/WARN/INFO
### Connection Flow
1. User opens logs view
2. Connect to `/ws/logs?pod=X&ns=Y`
3. Backend:
- Start kubectl logs stream
- Forward to WebSocket
4. Frontend:
- Append lines to buffer
- Auto-scroll if at bottom
- Colorize based on level
### Log Parsing
Each log line should include:
- Timestamp
- Log level (ERROR/WARN/INFO/DEBUG)
- Message
Format:
```typescript
interface LogLine {
timestamp: string; // ISO 8601
level: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
message: string;
}
```
### Color Coding
- **ERROR**: `#ef6f6f`
- **WARN**: `#e8b54a`
- **INFO**: `#b9c6d8`
- **DEBUG**: `#6b7280`
### Auto-Scroll Logic
- **Track scroll position**: Store `scrollTop`
- **At bottom**: Auto-scroll on new log
- **Scrolled up**: Don't auto-scroll
- **Threshold**: Within 20px of bottom = "at bottom"
### User Controls
- **Click auto-scroll toggle**: Switch between auto and manual scroll
- **Clear logs**: Button to clear buffer
- **Copy log line**: Right-click menu
- **Filter**: Simple filtering (show/hide levels)
### State Management
```typescript
interface LogsState {
lines: LogEntry[];
scrollTop: number;
connected: boolean;
autoScroll: boolean;
timestamp: boolean;
logLines: LogLine[];
scrollPosition: number;
filterLevels: Set<LogLevel>;
}
```
---
### Streaming Considerations
- **Backpressure**: Buffer if client slow
- **Rate limiting**: Server can throttle logs
- **Keep-alive**: Ping every 30s
- **Reconnect**: Exponential backoff on disconnect
### Performance Optimization
- **Virtualization**: Only render visible lines
- **Batching**: Flush logs in batches if high volume
- **Throttling**: 16ms throttle for updates
- **Cleanup**: Remove old logs (keep last 1000 lines)
## Shared Features
### View State
- Both views persist state in krate.window.state
- reconnect: Auto reconnect on disconnect
- timestamps: Show/hide timestamps
- wrap: Word wrap toggle
### Tab Switching
- Shell tab: Enabled for pods only
- Logs tab: Enabled for pods/workloads
- Empty state: Show helpful message for unsupported resources
### Close Behavior
- Shell: Keep buffer, show "Disconnnected"
- Logs: Stop streaming, keep buffer
- Cleanup: Close WebSocket, cancel watchers
## WebSocket Protocol
### Shell
```
// Client → Server
{
type: 'shell:input',
data: string // Key press data
}
{
type: 'shell:resize',
cols: number,
rows: number
}
// Server → Client
{
type: 'shell:output',
data: string // stdout/stderr
}
{
type: 'shell:status',
connected: boolean,
message?: string
}
```
### Logs
```
// Client → Server
{
type: 'logs:subscribe',
pod: string,
namespace: string,
follow: boolean,
timestamps: boolean
}
{
type: 'logs:filter',
levels: string[]
}
// Server → Client
{
type: 'log',
log: LogLine
}
{
type: 'status',
connected: boolean,
message?: string
}
```
## 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)
1. **Space key**: Must distinguish between canvas pan and shell input
2. **Focus routing**: Route keys based on hovered window
3. **Scroll management**: Track scroll position for logs
4. **Terminal resize**: Debounce resize events
5. **Reconnection**: Handle WebSocket disconnects gracefully
6. **Memory**: Clean up old logs/shell buffers
7. **Authentication**: Validate user on WebSocket upgrade
8. **Permissions**: Enforce namespace RBAC on backend