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
46use crate :: { models:: Tunnel , styles:: colors} ;
57
68pub 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
1016impl 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
9790impl 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