Skip to content

Commit 7cb7ffc

Browse files
CopilotGZTimeWalker
andcommitted
Add tunnel management functionality to Connections view
Co-authored-by: GZTimeWalker <[email protected]>
1 parent 90c2d22 commit 7cb7ffc

File tree

1 file changed

+284
-64
lines changed

1 file changed

+284
-64
lines changed
Lines changed: 284 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,68 @@
11
// Connections view - Manage tunnels and connections
2-
use gpui::{Context, Render, SharedString, Window, div, prelude::*};
2+
use std::net::SocketAddr;
3+
4+
use gpui::{Context, Render, SharedString, Window, div, prelude::*, px};
35

46
use crate::{models::Tunnel, styles::colors};
57

68
pub struct ConnectionsView {
79
tunnels: Vec<Tunnel>,
10+
show_add_modal: bool,
11+
new_tunnel_name: String,
12+
new_tunnel_local: String,
13+
new_tunnel_remote: String,
814
}
915

1016
impl ConnectionsView {
1117
pub fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
1218
Self {
1319
tunnels: Vec::new(),
20+
show_add_modal: false,
21+
new_tunnel_name: String::new(),
22+
new_tunnel_local: String::from("127.0.0.1:8080"),
23+
new_tunnel_remote: String::from("ws://example.com"),
1424
}
1525
}
1626

17-
fn render_tunnel_item(&self, tunnel: &Tunnel, index: usize) -> impl IntoElement {
18-
let id = SharedString::from(format!("tunnel-{}", index));
19-
let status_color = if tunnel.enabled {
20-
colors::success()
21-
} else {
22-
gpui::rgba(0x888888FF)
23-
};
27+
fn add_tunnel(&mut self, cx: &mut Context<Self>) {
28+
// Parse addresses
29+
let local_addr: Result<SocketAddr, _> = self.new_tunnel_local.parse();
30+
let remote_addr: Result<SocketAddr, _> = self.new_tunnel_remote.parse();
2431

25-
div()
26-
.id(id)
27-
.flex()
28-
.items_center()
29-
.justify_between()
30-
.px_4()
31-
.py_3()
32-
.mb_2()
33-
.bg(gpui::rgba(0x2A2A2AFF))
34-
.rounded_md()
35-
.hover(|div| div.bg(gpui::rgba(0x333333FF)))
36-
.child(
37-
div()
38-
.flex()
39-
.items_center()
40-
.gap_3()
41-
.child(div().w_3().h_3().rounded_full().bg(status_color))
42-
.child(
43-
div()
44-
.flex()
45-
.flex_col()
46-
.gap_1()
47-
.child(
48-
div()
49-
.text_color(colors::foreground())
50-
.child(tunnel.name.clone()),
51-
)
52-
.child(
53-
div()
54-
.text_sm()
55-
.text_color(gpui::rgba(0xAAAAAAFF))
56-
.child(format!(
57-
"{} → {}",
58-
tunnel.local_addr, tunnel.remote_addr
59-
)),
60-
),
61-
),
62-
)
63-
.child(
64-
div()
65-
.text_sm()
66-
.text_color(gpui::rgba(0xAAAAAAFF))
67-
.child(if tunnel.enabled {
68-
"Enabled"
69-
} else {
70-
"Disabled"
71-
}),
72-
)
32+
if let (Ok(local), Ok(remote)) = (local_addr, remote_addr) {
33+
let tunnel = Tunnel {
34+
id: format!("tunnel-{}", self.tunnels.len()),
35+
name: if self.new_tunnel_name.is_empty() {
36+
format!("Tunnel {}", self.tunnels.len() + 1)
37+
} else {
38+
self.new_tunnel_name.clone()
39+
},
40+
local_addr: local,
41+
remote_addr: remote,
42+
enabled: true,
43+
};
44+
45+
self.tunnels.push(tunnel);
46+
self.show_add_modal = false;
47+
self.new_tunnel_name.clear();
48+
self.new_tunnel_local = String::from("127.0.0.1:8080");
49+
self.new_tunnel_remote = String::from("ws://example.com");
50+
cx.notify();
51+
}
52+
}
53+
54+
fn remove_tunnel(&mut self, index: usize, cx: &mut Context<Self>) {
55+
if index < self.tunnels.len() {
56+
self.tunnels.remove(index);
57+
cx.notify();
58+
}
59+
}
60+
61+
fn toggle_tunnel(&mut self, index: usize, cx: &mut Context<Self>) {
62+
if let Some(tunnel) = self.tunnels.get_mut(index) {
63+
tunnel.enabled = !tunnel.enabled;
64+
cx.notify();
65+
}
7366
}
7467

7568
fn render_empty_state(&self) -> impl IntoElement {
@@ -95,7 +88,7 @@ impl ConnectionsView {
9588
}
9689

9790
impl Render for ConnectionsView {
98-
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
91+
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
9992
div()
10093
.flex()
10194
.flex_col()
@@ -123,24 +116,251 @@ impl Render for ConnectionsView {
123116
.rounded_md()
124117
.cursor_pointer()
125118
.hover(|div| div.bg(gpui::rgba(0x0088DDFF)))
119+
.on_click(cx.listener(|this, _event, _window, cx| {
120+
this.show_add_modal = true;
121+
cx.notify();
122+
}))
126123
.child("+ Add Tunnel"),
127124
),
128125
)
129126
.child(if self.tunnels.is_empty() {
130127
self.render_empty_state().into_any_element()
131128
} else {
132-
let elements: Vec<_> = self
133-
.tunnels
134-
.iter()
135-
.enumerate()
136-
.map(|(i, tunnel)| self.render_tunnel_item(tunnel, i))
137-
.collect();
138129
div()
139130
.flex()
140131
.flex_col()
141132
.gap_2()
142-
.children(elements)
133+
.children(
134+
self.tunnels
135+
.iter()
136+
.enumerate()
137+
.map(|(index, tunnel)| {
138+
let id = SharedString::from(format!("tunnel-{}", index));
139+
let status_color = if tunnel.enabled {
140+
colors::success()
141+
} else {
142+
gpui::rgba(0x888888FF)
143+
};
144+
145+
div()
146+
.id(id)
147+
.flex()
148+
.items_center()
149+
.justify_between()
150+
.px_4()
151+
.py_3()
152+
.mb_2()
153+
.bg(gpui::rgba(0x2A2A2AFF))
154+
.rounded_md()
155+
.hover(|div| div.bg(gpui::rgba(0x333333FF)))
156+
.child(
157+
div()
158+
.flex()
159+
.items_center()
160+
.gap_3()
161+
.child(div().w_3().h_3().rounded_full().bg(status_color))
162+
.child(
163+
div()
164+
.flex()
165+
.flex_col()
166+
.gap_1()
167+
.child(
168+
div()
169+
.text_color(colors::foreground())
170+
.child(tunnel.name.clone()),
171+
)
172+
.child(
173+
div()
174+
.text_sm()
175+
.text_color(gpui::rgba(0xAAAAAAFF))
176+
.child(format!(
177+
"{} → {}",
178+
tunnel.local_addr, tunnel.remote_addr
179+
)),
180+
),
181+
),
182+
)
183+
.child(
184+
div()
185+
.flex()
186+
.gap_2()
187+
.child(
188+
div()
189+
.id(SharedString::from(format!("toggle-{}", index)))
190+
.px_3()
191+
.py_1()
192+
.rounded_md()
193+
.text_sm()
194+
.cursor_pointer()
195+
.bg(if tunnel.enabled {
196+
gpui::rgba(0x28A745FF)
197+
} else {
198+
gpui::rgba(0x555555FF)
199+
})
200+
.hover(|div| {
201+
div.bg(if tunnel.enabled {
202+
gpui::rgba(0x218838FF)
203+
} else {
204+
gpui::rgba(0x666666FF)
205+
})
206+
})
207+
.on_click(cx.listener(move |this, _event, _window, cx| {
208+
this.toggle_tunnel(index, cx);
209+
}))
210+
.child(if tunnel.enabled { "Enabled" } else { "Disabled" }),
211+
)
212+
.child(
213+
div()
214+
.id(SharedString::from(format!("delete-{}", index)))
215+
.px_3()
216+
.py_1()
217+
.rounded_md()
218+
.text_sm()
219+
.cursor_pointer()
220+
.bg(colors::error())
221+
.hover(|div| div.bg(gpui::rgba(0xFF6655FF)))
222+
.on_click(cx.listener(move |this, _event, _window, cx| {
223+
this.remove_tunnel(index, cx);
224+
}))
225+
.child("Delete"),
226+
),
227+
)
228+
})
229+
.collect::<Vec<_>>(),
230+
)
143231
.into_any_element()
144232
})
233+
.when(self.show_add_modal, |div| {
234+
div.child(self.render_add_modal(cx))
235+
})
236+
}
237+
}
238+
239+
impl ConnectionsView {
240+
fn render_add_modal(&self, cx: &mut Context<Self>) -> impl IntoElement {
241+
div()
242+
.absolute()
243+
.top_0()
244+
.left_0()
245+
.right_0()
246+
.bottom_0()
247+
.flex()
248+
.items_center()
249+
.justify_center()
250+
.bg(gpui::rgba(0x00000080))
251+
.child(
252+
div()
253+
.bg(colors::background())
254+
.rounded_lg()
255+
.p_6()
256+
.w(px(400.0))
257+
.flex()
258+
.flex_col()
259+
.gap_4()
260+
.child(
261+
div()
262+
.text_xl()
263+
.text_color(colors::foreground())
264+
.child("Add New Tunnel"),
265+
)
266+
.child(
267+
div()
268+
.flex()
269+
.flex_col()
270+
.gap_2()
271+
.child(
272+
div()
273+
.text_sm()
274+
.text_color(colors::foreground())
275+
.child("Tunnel Name"),
276+
)
277+
.child(
278+
div()
279+
.px_3()
280+
.py_2()
281+
.rounded_md()
282+
.bg(gpui::rgba(0x2A2A2AFF))
283+
.text_color(colors::foreground())
284+
.child("Tunnel 1"),
285+
),
286+
)
287+
.child(
288+
div()
289+
.flex()
290+
.flex_col()
291+
.gap_2()
292+
.child(
293+
div()
294+
.text_sm()
295+
.text_color(colors::foreground())
296+
.child("Local Address"),
297+
)
298+
.child(
299+
div()
300+
.px_3()
301+
.py_2()
302+
.rounded_md()
303+
.bg(gpui::rgba(0x2A2A2AFF))
304+
.text_color(colors::foreground())
305+
.child(self.new_tunnel_local.clone()),
306+
),
307+
)
308+
.child(
309+
div()
310+
.flex()
311+
.flex_col()
312+
.gap_2()
313+
.child(
314+
div()
315+
.text_sm()
316+
.text_color(colors::foreground())
317+
.child("Remote Address"),
318+
)
319+
.child(
320+
div()
321+
.px_3()
322+
.py_2()
323+
.rounded_md()
324+
.bg(gpui::rgba(0x2A2A2AFF))
325+
.text_color(colors::foreground())
326+
.child(self.new_tunnel_remote.clone()),
327+
),
328+
)
329+
.child(
330+
div()
331+
.flex()
332+
.gap_2()
333+
.justify_end()
334+
.child(
335+
div()
336+
.id("cancel-button")
337+
.px_4()
338+
.py_2()
339+
.rounded_md()
340+
.bg(gpui::rgba(0x444444FF))
341+
.cursor_pointer()
342+
.hover(|div| div.bg(gpui::rgba(0x555555FF)))
343+
.on_click(cx.listener(|this, _event, _window, cx| {
344+
this.show_add_modal = false;
345+
cx.notify();
346+
}))
347+
.child("Cancel"),
348+
)
349+
.child(
350+
div()
351+
.id("add-button")
352+
.px_4()
353+
.py_2()
354+
.rounded_md()
355+
.bg(colors::accent())
356+
.cursor_pointer()
357+
.hover(|div| div.bg(gpui::rgba(0x0088DDFF)))
358+
.on_click(cx.listener(|this, _event, _window, cx| {
359+
this.add_tunnel(cx);
360+
}))
361+
.child("Add"),
362+
),
363+
),
364+
)
145365
}
146366
}

0 commit comments

Comments
 (0)