1
1
import type { WorkspaceApp } from "api/typesGenerated" ;
2
2
import { Button } from "components/Button/Button" ;
3
+ import { Input } from "components/Input/Input" ;
4
+ import { Switch } from "components/Switch/Switch" ;
3
5
import {
4
6
DropdownMenu ,
5
7
DropdownMenuContent ,
@@ -8,11 +10,18 @@ import {
8
10
} from "components/DropdownMenu/DropdownMenu" ;
9
11
import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
10
12
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip" ;
11
- import { ChevronDownIcon , LayoutGridIcon } from "lucide-react" ;
13
+ import {
14
+ ChevronDownIcon ,
15
+ LayoutGridIcon ,
16
+ ShieldIcon ,
17
+ PlusIcon ,
18
+ TrashIcon ,
19
+ SaveIcon ,
20
+ } from "lucide-react" ;
12
21
import { useAppLink } from "modules/apps/useAppLink" ;
13
22
import type { Task } from "modules/tasks/tasks" ;
14
23
import type React from "react" ;
15
- import { type FC , useState } from "react" ;
24
+ import { type FC , useState , useCallback } from "react" ;
16
25
import { Link as RouterLink } from "react-router-dom" ;
17
26
import { cn } from "utils/cn" ;
18
27
import { TaskAppIFrame } from "./TaskAppIframe" ;
@@ -22,6 +31,56 @@ type TaskAppsProps = {
22
31
} ;
23
32
24
33
export const TaskApps : FC < TaskAppsProps > = ( { task } ) => {
34
+ const [ allowedCommands , setAllowedCommands ] = useState < string [ ] > ( [
35
+ "npm run dev" ,
36
+ "npm run test" ,
37
+ ] ) ;
38
+ const [ allowedHosts , setAllowedHosts ] = useState < string [ ] > ( [
39
+ "artifacts.corporate.com" ,
40
+ ] ) ;
41
+ const [ auditingEnabled , setAuditingEnabled ] = useState < boolean > ( true ) ;
42
+
43
+ const handleCommandChange = useCallback ( ( index : number , value : string ) => {
44
+ setAllowedCommands ( ( prev ) => {
45
+ const updated = [ ...prev ] ;
46
+ updated [ index ] = value ;
47
+ return updated ;
48
+ } ) ;
49
+ } , [ ] ) ;
50
+
51
+ const removeCommand = useCallback ( ( index : number ) => {
52
+ setAllowedCommands ( ( prev ) => prev . filter ( ( _ , i ) => i !== index ) ) ;
53
+ } , [ ] ) ;
54
+
55
+ const addCommand = useCallback ( ( ) => {
56
+ setAllowedCommands ( ( prev ) => [ ...prev , "" ] ) ;
57
+ } , [ ] ) ;
58
+
59
+ const handleHostChange = useCallback ( ( index : number , value : string ) => {
60
+ setAllowedHosts ( ( prev ) => {
61
+ const updated = [ ...prev ] ;
62
+ updated [ index ] = value ;
63
+ return updated ;
64
+ } ) ;
65
+ } , [ ] ) ;
66
+
67
+ const removeHost = useCallback ( ( index : number ) => {
68
+ setAllowedHosts ( ( prev ) => prev . filter ( ( _ , i ) => i !== index ) ) ;
69
+ } , [ ] ) ;
70
+
71
+ const addHost = useCallback ( ( ) => {
72
+ setAllowedHosts ( ( prev ) => [ ...prev , "" ] ) ;
73
+ } , [ ] ) ;
74
+
75
+ const saveSettings = useCallback ( ( ) => {
76
+ // Here you would typically save the settings to your backend
77
+ console . log ( "Saving security settings:" , {
78
+ allowedCommands,
79
+ allowedHosts,
80
+ auditingEnabled,
81
+ } ) ;
82
+ // For now, we just log to console as this is a prototype
83
+ } , [ allowedCommands , allowedHosts , auditingEnabled ] ) ;
25
84
const agents = task . workspace . latest_build . resources
26
85
. flatMap ( ( r ) => r . agents )
27
86
. filter ( ( a ) => ! ! a ) ;
@@ -75,8 +134,110 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
75
134
) ) }
76
135
</ div >
77
136
78
- { externalApps . length > 0 && (
79
- < div className = "ml-auto" >
137
+ < div className = "ml-auto flex gap-2" >
138
+ < DropdownMenu >
139
+ < DropdownMenuTrigger asChild >
140
+ < Button size = "sm" variant = "subtle" >
141
+ < ShieldIcon className = "mr-1.5 h-4 w-4" />
142
+ Security
143
+ < ChevronDownIcon className = "ml-1" />
144
+ </ Button >
145
+ </ DropdownMenuTrigger >
146
+ < DropdownMenuContent align = "end" className = "w-[400px]" >
147
+ < div className = "p-4 space-y-4" >
148
+ < div className = "border-b border-border pb-2 mb-2 flex items-center justify-between" >
149
+ < h3 className = "text-lg font-medium" > Security Settings</ h3 >
150
+ </ div >
151
+
152
+ < div >
153
+ < h4 className = "text-sm font-medium mb-2" > Allowed Commands</ h4 >
154
+ < div className = "space-y-2" >
155
+ { allowedCommands . map ( ( command , index ) => (
156
+ < div key = { index } className = "flex items-center gap-2" >
157
+ < Input
158
+ className = "flex-1 font-mono text-sm"
159
+ value = { command }
160
+ onChange = { ( e ) =>
161
+ handleCommandChange ( index , e . target . value )
162
+ }
163
+ />
164
+ < Button
165
+ size = "icon"
166
+ variant = "outline"
167
+ onClick = { ( ) => removeCommand ( index ) }
168
+ >
169
+ < TrashIcon className = "h-4 w-4" />
170
+ < span className = "sr-only" > Remove</ span >
171
+ </ Button >
172
+ </ div >
173
+ ) ) }
174
+ < Button
175
+ size = "sm"
176
+ variant = "outline"
177
+ className = "w-full mt-1"
178
+ onClick = { addCommand }
179
+ >
180
+ < PlusIcon className = "h-4 w-4 mr-1" />
181
+ Add Command
182
+ </ Button >
183
+ </ div >
184
+ </ div >
185
+
186
+ < div className = "mt-4" >
187
+ < h4 className = "text-sm font-medium mb-2" > Allowed Hosts</ h4 >
188
+ < div className = "space-y-2" >
189
+ { allowedHosts . map ( ( host , index ) => (
190
+ < div key = { index } className = "flex items-center gap-2" >
191
+ < Input
192
+ className = "flex-1 font-mono text-sm"
193
+ value = { host }
194
+ onChange = { ( e ) =>
195
+ handleHostChange ( index , e . target . value )
196
+ }
197
+ />
198
+ < Button
199
+ size = "icon"
200
+ variant = "outline"
201
+ onClick = { ( ) => removeHost ( index ) }
202
+ >
203
+ < TrashIcon className = "h-4 w-4" />
204
+ < span className = "sr-only" > Remove</ span >
205
+ </ Button >
206
+ </ div >
207
+ ) ) }
208
+ < Button
209
+ size = "sm"
210
+ variant = "outline"
211
+ className = "w-full mt-1"
212
+ onClick = { addHost }
213
+ >
214
+ < PlusIcon className = "h-4 w-4 mr-1" />
215
+ Add Host
216
+ </ Button >
217
+ </ div >
218
+ </ div >
219
+
220
+ < div className = "flex items-center justify-between mt-4" >
221
+ < span className = "text-sm font-medium" > Auditing</ span >
222
+ < Switch
223
+ checked = { auditingEnabled }
224
+ onCheckedChange = { setAuditingEnabled }
225
+ />
226
+ </ div >
227
+
228
+ < Button
229
+ className = "w-full mt-4"
230
+ size = "sm"
231
+ onClick = { saveSettings }
232
+ >
233
+ < SaveIcon className = "h-4 w-4 mr-1.5" />
234
+ Save Settings
235
+ </ Button >
236
+ </ div >
237
+ </ DropdownMenuContent >
238
+ </ DropdownMenu >
239
+
240
+ { externalApps . length > 0 && (
80
241
< DropdownMenu >
81
242
< DropdownMenuTrigger asChild >
82
243
< Button size = "sm" variant = "subtle" >
@@ -106,8 +267,8 @@ export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
106
267
} ) }
107
268
</ DropdownMenuContent >
108
269
</ DropdownMenu >
109
- </ div >
110
- ) }
270
+ ) }
271
+ </ div >
111
272
</ div >
112
273
113
274
< div className = "flex-1" >
0 commit comments