Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ and find the binary in `target/release`.

## Further Reading

If you want to intergrate `wsrx` in your own server project, you can read the [crate docs](https://docs.rs/crate/wsrx/latest).
If you want to integrate `wsrx` in your own server project, you can read the [crate docs](https://docs.rs/crate/wsrx/latest).

Also, `wsrx` is a simple tool that using plain WebSocket protocol to tunnel TCP connections,
so you can implement your own server / client in other languages you like.
Expand Down
195 changes: 195 additions & 0 deletions crates/wsrx-desktop-gpui/ACTUAL_ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Actual Architecture Analysis from Slint Project

## Core Concept: Scope-Based Architecture

The application is **scope-centric**, not page-centric. Each "scope" represents a domain/site that can control tunnels.

### Scope Types
1. **Default Scope** (`default-scope`): User-controlled, manual tunnel creation
2. **External Scopes**: Remote domains that request control (e.g., `gzctf.example.com`)

### Sidebar Navigation
The sidebar does NOT show pages. It shows:
1. "Get Started" - Main page with tunnel creation form
2. "Network Logs" - Tracing logs page
3. **Separator**
4. "Default Scope" - User's manual tunnels
5. **Dynamic Scope List** - External domains (with status icons)
6. **Separator**
7. "Settings" - Configuration
8. "Controller Port" - API status button

### Page/View Structure

#### 1. Get Started Page (`home`)
**Purpose**: Primary tunnel creation interface

**Components**:
- Application branding with animated cursor
- Update notification button (if available)
- **Network interface selector dropdown** (127.0.0.1, 0.0.0.0, LAN IPs)
- **Port input field**
- **Remote WebSocket address input** (ws:// or wss://)
- **Send button** - Creates tunnel and navigates to default-scope

**NOT a modal - this IS the main page**

#### 2. Connections Page (Dynamic, scope-specific)
**Purpose**: Display tunnels for current scope

**Displayed for**: Any scope page (default-scope or external domain)

**Header Section**:
- Scope icon (globe-star for default, globe-warning if pending, lock-closed if allowed)
- Scope name and host
- Control type badge (Manually Controlled vs External Controlled)
- Features list (e.g., "basic", "basic,pingfall")
- **Accept/Decline buttons** (for pending external scopes)
- **Remove button** (for allowed external scopes)

**Instance List** (scrollable):
- Each tunnel shows:
- Label (custom name)
- Local address (clickable to copy)
- Remote address
- Latency in ms (or "--" if connecting)
- Click anywhere to copy local address
- Click latency area to close tunnel

**Behavior**:
- Default scope: User creates tunnels from Get Started page
- External scopes: Remote API creates tunnels, user accepts/declines scope access

#### 3. Network Logs Page (`logs`)
**Purpose**: Real-time tracing log display

**Features**:
- Streams from `wsrx.log` file (JSON format)
- Displays logs with:
- Level badge (DEBUG/INFO/WARN/ERROR) with color
- Target module name
- Timestamp
- Message (word-wrapped)
- Opacity varies by level (DEBUG=0.5, INFO=0.8, WARN/ERROR=1.0)
- Separator lines between entries

**NOT sample data - reads actual tracing logs**

#### 4. Settings Page (`settings`)
**Purpose**: Configuration and about info

**Sections**:
- Theme selector
- Language selector
- Running in tray toggle
- System information display
- Version info

### Data Models

#### Instance (Tunnel)
```rust
pub struct Instance {
label: String, // Display name
remote: String, // ws:// or wss:// address
local: String, // IP:port
latency: i32, // -1 if not connected
scope_host: String, // Which scope owns this tunnel
}
```

#### Scope
```rust
pub struct Scope {
host: String, // Domain name (unique ID)
name: String, // Display name
state: String, // "pending", "allowed", "syncing"
features: String, // Comma-separated: "basic", "pingfall"
settings: HashMap, // Feature-specific config
}
```

#### Log Entry
```rust
pub struct Log {
timestamp: String, // "2025-11-10 15:30:45"
level: String, // "DEBUG", "INFO", "WARN", "ERROR"
target: String, // Module path (e.g., "wsrx::tunnel")
message: String, // Log message
}
```

### Bridges (State Management)

#### WindowControlBridge
- Window control actions (drag, minimize, maximize, close)

#### SystemInfoBridge
- OS type, version, has_updates
- Network interfaces list
- **Logs array** (for Network Logs page)
- Callbacks: refresh_interfaces, open_link, open_logs

#### InstanceBridge
- **instances**: All tunnels across all scopes
- **scoped_instances**: Filtered tunnels for current scope
- Callbacks:
- `add(remote, local)`: Create tunnel (default-scope only)
- `del(local)`: Delete tunnel

#### ScopeBridge
- **scopes**: Array of all external scopes
- Callbacks:
- `allow(host)`: Accept external scope
- `del(host)`: Remove/decline scope

#### SettingsBridge
- theme, language, running_in_tray
- api_port, online (daemon status)

#### UiState (Global State)
- **page**: Current page ID (string)
- "home" - Get Started
- "logs" - Network Logs
- "settings" - Settings
- "default-scope" - User's tunnels
- `<domain>` - External scope (e.g., "gzctf.example.com")
- **scope**: Current scope object (for connections page)
- **show_sidebar**: Boolean
- Callbacks:
- `change_scope(host)`: Updates scope and filters scoped_instances

### Navigation Flow

1. **App starts** → "home" page (Get Started)
2. **User creates tunnel** → Navigates to "default-scope" page
3. **External domain requests** → New scope appears in sidebar with "pending" status
4. **User clicks pending scope** → Shows connections page with Accept/Decline buttons
5. **User accepts** → Scope state becomes "allowed", shows tunnels
6. **Click "Network Logs"** → Shows logs page
7. **Click "Settings"** → Shows settings page

### Key Differences from Initial Implementation

#### ❌ What I Got Wrong:
1. Tunnel creation via modal dialog - **WRONG**, it's the main Get Started page
2. Connections page as standalone - **WRONG**, it's scope-specific
3. Sample log data - **WRONG**, must stream from tracing log file
4. Static page navigation - **WRONG**, sidebar shows scopes not pages
5. Separate pages for each view - **PARTIALLY WRONG**, connections page is dynamic

#### ✅ What to Keep:
- GPUI entity-based state management
- Component library (buttons, inputs, etc.)
- Vertical scrolling implementation
- Window controls and title bar

### Implementation Priority

1. **Phase 1**: Correct data models (Scope, Instance, Log with proper fields)
2. **Phase 2**: Implement UiState global state with scope management
3. **Phase 3**: Rewrite Get Started page as tunnel creation form
4. **Phase 4**: Make Connections page scope-aware with accept/decline
5. **Phase 5**: Implement tracing log subscriber and file streaming
6. **Phase 6**: Update sidebar to show scopes dynamically
7. **Phase 7**: Wire up bridges for actual daemon communication
5 changes: 3 additions & 2 deletions crates/wsrx-desktop-gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ toml = { workspace = true }
# Error handling and utilities
anyhow = "1.0"
chrono = { workspace = true }
phf = { version = "0.13", features = ["macros"] }
thiserror = { workspace = true }
url = { workspace = true }

Expand All @@ -66,9 +67,9 @@ tower-http = { workspace = true }
[build-dependencies]
build-target = { workspace = true }
git-version = { workspace = true }
rust-i18n = "3"
rustc_version = { workspace = true }
winres = { workspace = true }
rust-i18n = "3"

[[bin]]
name = "wsrx-desktop-gpui"
Expand All @@ -85,5 +86,5 @@ short_description = "Controlled TCP-over-WebSocket forwarding tunnel (GPUI Editi
icon = [
"../../arts/logo.png",
"../../macos/WebSocketReflectorX.icns",
"../../windows/WebSocketReflectorX.ico",
"../../windows/WebSocketReflectorX.ico"
]
68 changes: 68 additions & 0 deletions crates/wsrx-desktop-gpui/MIGRATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
# WebSocketReflectorX GPUI Migration Plan

## ⚠️ CORRECTED ARCHITECTURE (2025-11-10)

**See `ACTUAL_ARCHITECTURE.md` for detailed analysis.**

### Key Corrections from Original Slint Analysis:

1. **Application is Scope-Centric, NOT Page-Centric**
- Sidebar shows **scopes** (domains), not pages
- Each scope has its own connections view
- "Default Scope" for user-created tunnels
- External scopes for domain-controlled tunnels

2. **Get Started Page IS the Tunnel Creation Form**
- NOT a modal dialog
- Main page with network interface selector, port input, remote address input
- Creating a tunnel navigates to "default-scope"

3. **Connections Page is Dynamic per Scope**
- Shows tunnels filtered by current scope
- Header shows scope status (pending/allowed)
- Accept/Decline buttons for pending scopes
- Each tunnel displays: label, local (copyable), remote, latency

4. **Network Logs Must Stream from Tracing**
- Reads `wsrx.log` JSON file
- NOT sample data
- Displays real-time tracing output

5. **Sidebar Structure**:
```
- Get Started (home)
- Network Logs (logs)
---
- Default Scope (default-scope)
- [External Scopes...] (dynamic)
---
- Settings (settings)
- Controller Port (API status)
```

### Data Models (Corrected):

```rust
pub struct Instance { // NOT "Tunnel"
label: String,
remote: String,
local: String,
latency: i32,
scope_host: String,
}

pub struct Scope {
host: String, // Unique ID
name: String, // Display name
state: String, // "pending" | "allowed" | "syncing"
features: String, // "basic,pingfall"
settings: HashMap,
}

pub struct UiState {
page: String, // "home", "logs", "settings", "default-scope", or scope.host
scope: Scope, // Current scope for connections page
show_sidebar: bool,
}
```

---

## Overview

This document provides a comprehensive migration plan for transitioning the WebSocketReflectorX desktop application from the Slint-based `crates/desktop` to the GPUI-based `crates/wsrx-desktop-gpui`.
Expand Down
2 changes: 2 additions & 0 deletions crates/wsrx-desktop-gpui/src/bridges/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// This module contains the bridges that connect the UI to the wsrx daemon and
// other services

#![allow(dead_code)] // Bridges defined for future implementation

pub mod daemon;
pub mod settings;
pub mod system_info;
2 changes: 1 addition & 1 deletion crates/wsrx-desktop-gpui/src/components/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl IntoElement for Checkbox {
box_div = box_div.child(
// Checkmark icon
svg()
.path("icons/checkmark.svg")
.path("checkmark")
.size(sizes::icon_xs())
.text_color(colors::window_bg()),
);
Expand Down
2 changes: 2 additions & 0 deletions crates/wsrx-desktop-gpui/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Components - Reusable UI elements built with GPUI
// These are lower-level components used across different views

#![allow(dead_code)] // Components defined for future use

pub mod button;
pub mod checkbox;
pub mod icon_button;
Expand Down
2 changes: 2 additions & 0 deletions crates/wsrx-desktop-gpui/src/components/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Component prelude - Common imports for all components
// Following Zed's pattern from crates/ui/src/component_prelude.rs

#![allow(unused_imports)] // Prelude exports for convenience

pub use gpui::{
App, AppContext, InteractiveElement, IntoElement, ParentElement, SharedString,
StatefulInteractiveElement, Styled, Window, div, prelude::*, svg,
Expand Down
6 changes: 2 additions & 4 deletions crates/wsrx-desktop-gpui/src/components/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ where
.unwrap_or_else(|| self.placeholder.clone());

let disabled = self.disabled;
let options = self.options.clone();

div()
.id(self.id.clone())
Expand All @@ -124,10 +123,9 @@ where

if let (Some(index), Some(callback)) =
(this.selected_index, &this.on_select)
&& let Some(item) = this.options.get(index).cloned()
{
if let Some(item) = this.options.get(index).cloned() {
callback(window, cx, item);
}
callback(window, cx, item);
}

cx.notify();
Expand Down
5 changes: 2 additions & 3 deletions crates/wsrx-desktop-gpui/src/components/title_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl TitleBar {

impl Render for TitleBar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let window = self.window.clone();
let window = self.window;
let is_macos = cfg!(target_os = "macos");

div()
Expand All @@ -39,7 +39,6 @@ impl Render for TitleBar {
.bg(gpui::transparent_black())
// Drag area
.on_mouse_down(MouseButton::Left, {
let window = window.clone();
cx.listener(move |_this, _event: &MouseDownEvent, _window, cx| {
window
.update(cx, |_view, window, _cx| {
Expand Down Expand Up @@ -72,7 +71,7 @@ impl Render for TitleBar {
}))
.child(
svg()
.path("icons/navigation.svg")
.path("navigation")
.size(styles::sizes::icon_sm())
.text_color(styles::colors::window_fg()),
),
Expand Down
2 changes: 2 additions & 0 deletions crates/wsrx-desktop-gpui/src/components/traits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Component traits - Common interfaces for UI components
// Following Zed's pattern for reusable component behavior

#![allow(dead_code)] // Traits defined for future use

use gpui::{Context, Window};

/// Trait for components that can be clicked
Expand Down
Loading