diff --git a/design/admin-drawer.md b/design/admin-drawer.md index 00c38fd..a34acc6 100644 --- a/design/admin-drawer.md +++ b/design/admin-drawer.md @@ -1,4 +1,4 @@ -# Admin Drawer Feature Specification +# Design Document: Admin Drawer Feature Specification ## Overview The admin drawer is a right-side panel that displays all users connected to the current yard (shared cluster workspace). It enables administrators to see real-time user activity and spectate on other users' workspaces. @@ -34,7 +34,7 @@ The admin drawer is a right-side panel that displays all users connected to the For each connected user (including self): ### Avatar Section -- Circle: 40px直径 +- Circle: 40px diameter - Background: User color from palette - Content: - For self: `★` (star) diff --git a/design/backend.md b/design/backend.md index b65c9c3..0c21b1c 100644 --- a/design/backend.md +++ b/design/backend.md @@ -1,4 +1,4 @@ -# Backend & Real-Time Sync Feature Specification +# Design Document: Backend & Real-Time Sync Feature Specification ## Overview The backend service aggregates Kubernetes API data and provides real-time WebSocket connections for shell, logs, and multi-user collaboration. diff --git a/design/canvas.md b/design/canvas.md index ee22e86..17c15c6 100644 --- a/design/canvas.md +++ b/design/canvas.md @@ -1,4 +1,4 @@ -# Canvas Feature Specification +# Design Document: Canvas Feature Specification ## Overview The canvas is the infinite zoomable/pannable workspace where krates live. It serves as the primary interaction surface for exploring Kubernetes clusters. @@ -39,8 +39,8 @@ interface CanvasState { ## Navigation ### Scroll Wheel -- ** Default **: Pan canvas -- ** Ctrl/⌘ + scroll **: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`) +- **Default**: Pan canvas +- **Ctrl/⌘ + scroll**: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`) ### Space + Drag Panning - Critical: must work even when shell window has keyboard focus @@ -52,13 +52,13 @@ interface CanvasState { - Do NOT use `e.stopPropagation()` - allow event propagation ### Click/Typed Key Triggers -- ** Click on empty canvas **: Open spotlight -- ** Type any key **: Open spotlight with that character pre-seeded +- **Click on empty canvas**: Open spotlight +- **Type any key**: Open spotlight with that character pre-seeded ### Zoom Levels / LOD -- ** zoom > 0.5 **: Normal view — krates expanded, all windows visible, draggable -- ** zoom < 0.4 **: Collapsed view — krates become 230px wide overview cards, no windows rendered -- ** Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering) +- **zoom > 0.5**: Normal view — krates expanded, all windows visible, draggable +- **zoom < 0.4**: Collapsed view — krates become 230px wide overview cards, no windows rendered +- **Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering) ### Animations - CSS `transform` with `transition` only for programmatic "fly" animations: `.52s cubic-bezier(.22,.8,.28,1)` diff --git a/design/collection-window.md b/design/collection-window.md index b1ef742..bfde4d3 100644 --- a/design/collection-window.md +++ b/design/collection-window.md @@ -1,4 +1,4 @@ -# Collection Window Feature Specification +# Design Document: Collection Window Feature Specification ## Overview A collection window provides an overview of Kubernetes objects in a category (All Pods, All Services, etc.) or namespace. It supports both list and tree views with filtering and selection. @@ -132,7 +132,7 @@ Show as small icons or labels: ### Implementation - Event listener with `event.code` for layout safety - Open views in newly created krate (or existing if spotlight session) -- Shell tab disabled for non-exec types (opacity 0.45) +- Shell tab disabled for non-pod workloads (opacity 0.45) ## View-Specific Content diff --git a/design/detail-window.md b/design/detail-window.md index 388fd38..10756b0 100644 --- a/design/detail-window.md +++ b/design/detail-window.md @@ -1,4 +1,4 @@ -# Detail Window Feature Specification +# Design Document: Detail Window Feature Specification ## Overview A detail window shows one view (YAML / Describe / Logs / Shell) for one Kubernetes object. Windows are grouped into krates and can be resized/moved within their container. diff --git a/design/krate.md b/design/krate.md index d093a7f..252625e 100644 --- a/design/krate.md +++ b/design/krate.md @@ -1,4 +1,4 @@ -# Krate (Window Group) Feature Specification +# Design Document: Krate (Window Group) Feature Specification ## Overview A krate is a named group of detail windows for a single Kubernetes object or collection. Krates live on the canvas and serve as organizational units for related workspace views. diff --git a/design/minimap.md b/design/minimap.md index a396eac..4c1e5b1 100644 --- a/design/minimap.md +++ b/design/minimap.md @@ -1,4 +1,4 @@ -# Minimap Feature Specification +# Design Document: Minimap Feature Specification ## Overview The minimap provides a bird's-eye overview of all krates' positions on the canvas. It allows navigation by clicking to fly the camera to any location. diff --git a/design/shell-logs.md b/design/shell-logs.md index f74d9fb..5dec2ea 100644 --- a/design/shell-logs.md +++ b/design/shell-logs.md @@ -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 -
- {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
+### 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