@@ -6,17 +6,18 @@ import { type Interpolation, type Theme, useTheme } from "@emotion/react";
6
6
import type { FC } from "react" ;
7
7
import { useQuery } from "react-query" ;
8
8
import { docs } from "utils/docs" ;
9
- import { getAgentListeningPorts } from "api/api" ;
9
+ import { getAgentListeningPorts , getWorkspaceAgentSharedPorts } from "api/api" ;
10
10
import type {
11
11
WorkspaceAgent ,
12
12
WorkspaceAgentListeningPort ,
13
13
WorkspaceAgentListeningPortsResponse ,
14
+ WorkspaceAgentPortShare ,
15
+ WorkspaceAgentPortShares ,
14
16
} from "api/typesGenerated" ;
15
17
import { portForwardURL } from "utils/portForward" ;
16
18
import { type ClassName , useClassName } from "hooks/useClassName" ;
17
19
import {
18
20
HelpTooltipLink ,
19
- HelpTooltipLinksGroup ,
20
21
HelpTooltipText ,
21
22
HelpTooltipTitle ,
22
23
} from "components/HelpTooltip/HelpTooltip" ;
@@ -31,29 +32,28 @@ import Select from "@mui/material/Select";
31
32
import MenuItem from "@mui/material/MenuItem" ;
32
33
import FormControl from "@mui/material/FormControl" ;
33
34
import TextField from "@mui/material/TextField" ;
34
- import SensorsIcon from '@mui/icons-material/Sensors' ;
35
- import Add from '@mui/icons-material/Add' ;
36
- import IconButton from "@mui/material/IconButton" ;
37
- import LockIcon from '@mui/icons-material/Lock' ;
38
- import LockOpenIcon from '@mui/icons-material/LockOpen' ;
39
- import DeleteIcon from '@mui/icons-material/Delete' ;
35
+ import SensorsIcon from "@mui/icons-material/Sensors" ;
36
+ import LockIcon from "@mui/icons-material/Lock" ;
37
+ import LockOpenIcon from "@mui/icons-material/LockOpen" ;
40
38
41
39
export interface PortForwardButtonProps {
42
40
host : string ;
43
41
username : string ;
44
42
workspaceName : string ;
43
+ workspaceID : string ;
45
44
agent : WorkspaceAgent ;
46
45
47
46
/**
48
47
* Only for use in Storybook
49
48
*/
50
49
storybook ?: {
51
- portsQueryData ?: WorkspaceAgentListeningPortsResponse ;
50
+ listeningPortsQueryData ?: WorkspaceAgentListeningPortsResponse ;
51
+ sharedPortsQueryData ?: WorkspaceAgentPortShares ;
52
52
} ;
53
53
}
54
54
55
55
export const PortForwardButton : FC < PortForwardButtonProps > = ( props ) => {
56
- const { agent, storybook } = props ;
56
+ const { agent, workspaceID , storybook } = props ;
57
57
58
58
const paper = useClassName ( classNames . paper , [ ] ) ;
59
59
@@ -64,21 +64,34 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
64
64
refetchInterval : 5_000 ,
65
65
} ) ;
66
66
67
- const data = storybook ? storybook . portsQueryData : portsQuery . data ;
67
+ const sharedPortsQuery = useQuery ( {
68
+ queryKey : [ "sharedPorts" , agent . id ] ,
69
+ queryFn : ( ) => getWorkspaceAgentSharedPorts ( workspaceID ) ,
70
+ enabled : ! storybook && agent . status === "connected" ,
71
+ } ) ;
72
+
73
+ const listeningPorts = storybook
74
+ ? storybook . listeningPortsQueryData
75
+ : portsQuery . data ;
76
+ const sharedPorts = storybook
77
+ ? storybook . sharedPortsQueryData
78
+ : sharedPortsQuery . data ;
68
79
69
80
return (
70
81
< Popover >
71
82
< PopoverTrigger >
72
83
< Button
73
- disabled = { ! data }
84
+ disabled = { ! listeningPorts }
74
85
size = "small"
75
86
variant = "text"
76
87
endIcon = { < KeyboardArrowDown /> }
77
88
css = { { fontSize : 13 , padding : "8px 12px" } }
78
89
startIcon = {
79
- data ? (
90
+ listeningPorts ? (
80
91
< div >
81
- < span css = { styles . portCount } > { data . ports . length } </ span >
92
+ < span css = { styles . portCount } >
93
+ { listeningPorts . ports . length }
94
+ </ span >
82
95
</ div >
83
96
) : (
84
97
< CircularProgress size = { 10 } />
@@ -89,34 +102,30 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
89
102
</ Button >
90
103
</ PopoverTrigger >
91
104
< PopoverContent horizontal = "right" classes = { { paper } } >
92
- < PortForwardPopoverView { ...props } ports = { data ?. ports } />
105
+ < PortForwardPopoverView
106
+ { ...props }
107
+ listeningPorts = { listeningPorts ?. ports }
108
+ sharedPorts = { sharedPorts ?. shares }
109
+ />
93
110
</ PopoverContent >
94
111
</ Popover >
95
112
) ;
96
113
} ;
97
114
98
115
interface PortForwardPopoverViewProps extends PortForwardButtonProps {
99
- ports ?: WorkspaceAgentListeningPort [ ] ;
116
+ listeningPorts ?: WorkspaceAgentListeningPort [ ] ;
117
+ sharedPorts ?: WorkspaceAgentPortShare [ ] ;
100
118
}
101
119
102
120
export const PortForwardPopoverView : FC < PortForwardPopoverViewProps > = ( {
103
121
host,
104
122
workspaceName,
105
123
agent,
106
124
username,
107
- ports,
125
+ listeningPorts,
126
+ sharedPorts,
108
127
} ) => {
109
128
const theme = useTheme ( ) ;
110
- const sharedPorts = [
111
- {
112
- port : 8090 ,
113
- share_level : "Authenticated" ,
114
- } ,
115
- {
116
- port : 8091 ,
117
- share_level : "Public" ,
118
- }
119
- ] ;
120
129
121
130
return (
122
131
< >
@@ -126,18 +135,20 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
126
135
borderBottom : `1px solid ${ theme . palette . divider } ` ,
127
136
} }
128
137
>
129
- < Stack direction = "row" justifyContent = "space-between" alignItems = "start" >
130
- < HelpTooltipTitle > Listening ports</ HelpTooltipTitle >
138
+ < Stack
139
+ direction = "row"
140
+ justifyContent = "space-between"
141
+ alignItems = "start"
142
+ >
143
+ < HelpTooltipTitle > Listening ports</ HelpTooltipTitle >
131
144
< HelpTooltipLink href = { docs ( "/networking/port-forwarding#dashboard" ) } >
132
145
Learn more
133
146
</ HelpTooltipLink >
134
147
</ Stack >
135
148
< HelpTooltipText css = { { color : theme . palette . text . secondary } } >
136
- { ports ?. length === 0
149
+ { listeningPorts ?. length === 0
137
150
? "No open ports were detected."
138
- : "The listening ports are exclusively accessible to you."
139
- }
140
-
151
+ : "The listening ports are exclusively accessible to you." }
141
152
</ HelpTooltipText >
142
153
< form
143
154
css = { styles . newPortForm }
@@ -186,10 +197,11 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
186
197
</ Button >
187
198
</ form >
188
199
< div
189
- css = { {
190
- paddingTop : 10 ,
191
- } } >
192
- { ports ?. map ( ( port ) => {
200
+ css = { {
201
+ paddingTop : 10 ,
202
+ } }
203
+ >
204
+ { listeningPorts ?. map ( ( port ) => {
193
205
const url = portForwardURL (
194
206
host ,
195
207
port . port ,
@@ -200,7 +212,12 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
200
212
const label =
201
213
port . process_name !== "" ? port . process_name : port . port ;
202
214
return (
203
- < Stack key = { port . port } direction = "row" justifyContent = "space-between" alignItems = "center" >
215
+ < Stack
216
+ key = { port . port }
217
+ direction = "row"
218
+ justifyContent = "space-between"
219
+ alignItems = "center"
220
+ >
204
221
< Link
205
222
underline = "none"
206
223
css = { styles . portLink }
@@ -227,95 +244,94 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
227
244
) ;
228
245
} ) }
229
246
</ div >
230
- </ div >
231
- < div css = { {
247
+ </ div >
248
+ < div
249
+ css = { {
232
250
padding : 20 ,
233
- } } >
251
+ } }
252
+ >
234
253
< HelpTooltipTitle > Shared Ports</ HelpTooltipTitle >
235
254
< HelpTooltipText css = { { color : theme . palette . text . secondary } } >
236
- { ports ?. length === 0
255
+ { listeningPorts ?. length === 0
237
256
? "No ports are shared."
238
257
: "Ports can be shared with other Coder users or with the public." }
239
258
</ HelpTooltipText >
240
259
< div >
241
- { sharedPorts ?. map ( ( port ) => {
260
+ { sharedPorts ?. map ( ( share ) => {
242
261
const url = portForwardURL (
243
262
host ,
244
- port . port ,
263
+ share . port ,
245
264
agent . name ,
246
265
workspaceName ,
247
266
username ,
248
267
) ;
249
- const label = port . port ;
268
+ const label = share . port ;
250
269
return (
251
- < Stack key = { port . port } direction = "row" justifyContent = "space-between" alignItems = "center" >
270
+ < Stack
271
+ key = { share . port }
272
+ direction = "row"
273
+ justifyContent = "space-between"
274
+ alignItems = "center"
275
+ >
252
276
< Link
253
277
underline = "none"
254
278
css = { styles . portLink }
255
279
href = { url }
256
280
target = "_blank"
257
281
rel = "noreferrer"
258
282
>
259
- { port . share_level === "Public" ?
260
- (
283
+ { share . share_level === "public" ? (
261
284
< LockOpenIcon css = { { width : 14 , height : 14 } } />
262
- )
263
- : (
285
+ ) : (
264
286
< LockIcon css = { { width : 14 , height : 14 } } />
265
287
) }
266
288
{ label }
267
289
</ Link >
268
290
< Stack direction = "row" gap = { 1 } >
269
- < FormControl size = "small" >
270
- < Select
271
- sx = { {
272
- boxShadow : "none" ,
273
- ".MuiOutlinedInput-notchedOutline" : { border : 0 } ,
274
- "&.MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline" :
275
- {
276
- border : 0 ,
277
- } ,
278
- "&.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline" :
279
- {
280
- border : 0 ,
281
- } ,
282
- } }
283
- value = { port . share_level }
284
- >
285
- < MenuItem value = "Owner" > Owner</ MenuItem >
286
- < MenuItem value = "Authenticated" > Authenticated</ MenuItem >
287
- < MenuItem value = "Public" > Public</ MenuItem >
288
- </ Select >
289
- </ FormControl >
291
+ < FormControl size = "small" >
292
+ < Select
293
+ sx = { {
294
+ boxShadow : "none" ,
295
+ ".MuiOutlinedInput-notchedOutline" : { border : 0 } ,
296
+ "&.MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline" :
297
+ {
298
+ border : 0 ,
299
+ } ,
300
+ "&.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline" :
301
+ {
302
+ border : 0 ,
303
+ } ,
304
+ } }
305
+ value = { share . share_level }
306
+ >
307
+ < MenuItem value = "Authenticated" > Authenticated</ MenuItem >
308
+ < MenuItem value = "Public" > Public</ MenuItem >
309
+ </ Select >
310
+ </ FormControl >
290
311
</ Stack >
291
-
292
312
</ Stack >
293
313
) ;
294
314
} ) }
295
- </ div >
296
- < Stack direction = "column" gap = { 1 } justifyContent = "flex-end" sx = { {
297
- marginTop : 2 ,
298
- } } >
299
- < TextField
300
- label = "Port"
301
- variant = "outlined"
302
- size = "small"
303
- />
315
+ </ div >
316
+ < Stack
317
+ direction = "column"
318
+ gap = { 1 }
319
+ justifyContent = "flex-end"
320
+ sx = { {
321
+ marginTop : 2 ,
322
+ } }
323
+ >
324
+ < TextField label = "Port" variant = "outlined" size = "small" />
304
325
< FormControl size = "small" >
305
- < Select
306
- value = "Authenticated"
307
- >
308
- < MenuItem value = "Authenticated" > Authenticated</ MenuItem >
309
- < MenuItem value = "Public" > Public</ MenuItem >
310
- </ Select >
326
+ < Select value = "Authenticated" >
327
+ < MenuItem value = "Authenticated" > Authenticated</ MenuItem >
328
+ < MenuItem value = "Public" > Public</ MenuItem >
329
+ </ Select >
311
330
</ FormControl >
312
- < Button variant = "contained" >
313
- Add Shared Port
314
- </ Button >
331
+ < Button variant = "contained" > Add Shared Port</ Button >
315
332
</ Stack >
316
333
</ div >
317
334
318
-
319
335
{ /* <div css={{ padding: 20 }}>
320
336
<HelpTooltipTitle>Forward port</HelpTooltipTitle>
321
337
<HelpTooltipText css={{ color: theme.palette.text.secondary }}>
0 commit comments