4
4
* See License-AGPL.txt in the project root for license information.
5
5
*/
6
6
7
+ import { UserEnvVarValue } from "@gitpod/gitpod-protocol" ;
8
+ import { useEffect , useRef , useState } from "react" ;
9
+ import ContextMenu from "../components/ContextMenu" ;
10
+ import Modal from "../components/Modal" ;
11
+ import { getGitpodService } from "../service/service" ;
7
12
import { SettingsPage } from "./SettingsPage" ;
13
+ import ThreeDots from '../icons/ThreeDots.svg' ;
14
+
15
+ interface EnvVarModalProps {
16
+ envVar : UserEnvVarValue ;
17
+ onClose : ( ) => void ;
18
+ save : ( v : UserEnvVarValue ) => void ;
19
+ validate : ( v : UserEnvVarValue ) => string ;
20
+ }
21
+
22
+ function AddEnvVarModal ( p : EnvVarModalProps ) {
23
+ const [ ev , setEv ] = useState ( { ...p . envVar } ) ;
24
+ const [ error , setError ] = useState ( '' ) ;
25
+ const ref = useRef ( ev ) ;
26
+
27
+ const update = ( pev : Partial < UserEnvVarValue > ) => {
28
+ const newEnv = { ...ref . current , ... pev } ;
29
+ setEv ( newEnv ) ;
30
+ ref . current = newEnv ;
31
+ } ;
32
+
33
+ useEffect ( ( ) => {
34
+ setEv ( { ...p . envVar } ) ;
35
+ setError ( '' ) ;
36
+ } , [ p . envVar ] ) ;
37
+
38
+ const isNew = ! p . envVar . id ;
39
+ let save = ( ) => {
40
+ const v = ref . current ;
41
+ const errorMsg = p . validate ( v ) ;
42
+ if ( errorMsg !== '' ) {
43
+ setError ( errorMsg ) ;
44
+ return false ;
45
+ } else {
46
+ p . save ( v ) ;
47
+ p . onClose ( ) ;
48
+ return true ;
49
+ }
50
+ } ;
51
+
52
+ return < Modal visible = { true } onClose = { p . onClose } onEnter = { save } >
53
+ < h3 className = "pb-2" > { isNew ? 'New' : 'Edit' } Variable</ h3 >
54
+ < div className = "border-t -mx-6 px-6 py-2 flex flex-col" >
55
+ { error ? < div className = "bg-gitpod-kumquat-light rounded-md p-3 text-red-500 text-sm" >
56
+ { error }
57
+ </ div > : null }
58
+ < div className = "mt-4" >
59
+ < h4 > Name</ h4 >
60
+ < input className = "w-full" type = "text" value = { ev . name } onChange = { ( v ) => { update ( { name : v . target . value } ) } } />
61
+ </ div >
62
+ < div className = "mt-4" >
63
+ < h4 > Value</ h4 >
64
+ < input className = "w-full" type = "text" value = { ev . value } onChange = { ( v ) => { update ( { value : v . target . value } ) } } />
65
+ </ div >
66
+ < div className = "mt-4" >
67
+ < h4 > Scope</ h4 >
68
+ < input className = "w-full" type = "text" value = { ev . repositoryPattern } placeholder = "org/project"
69
+ onChange = { ( v ) => { update ( { repositoryPattern : v . target . value } ) } } />
70
+ </ div >
71
+ < div className = "mt-3" >
72
+ < p > You can pass a variable for a specific org/project or use wildcard characters (*/*) to make it available in more projects.</ p >
73
+ </ div >
74
+ </ div >
75
+ < div className = "flex justify-end mt-6" >
76
+ < button className = "text-gray-900 border-white bg-white hover:border-gray-200" onClick = { p . onClose } > Cancel</ button >
77
+ < button className = { "ml-2 disabled:opacity-50" } onClick = { save } > { isNew ? 'Add' : 'Update' } Variable</ button >
78
+ </ div >
79
+ </ Modal >
80
+ }
8
81
9
82
export default function EnvVars ( ) {
10
- return < div >
11
- < SettingsPage title = 'Variables' subtitle = 'Configure environment variables for all workspaces.' >
12
- < h3 > Environment Variables</ h3 >
13
- </ SettingsPage >
14
- </ div > ;
83
+ const [ envVars , setEnvVars ] = useState ( [ ] as UserEnvVarValue [ ] ) ;
84
+ const [ currentEnvVar , setCurrentEnvVar ] = useState ( { name : '' , value : '' , repositoryPattern : '' } as UserEnvVarValue ) ;
85
+ const [ isAddEnvVarModalVisible , setAddEnvVarModalVisible ] = useState ( false ) ;
86
+ const update = async ( ) => {
87
+ await getGitpodService ( ) . server . getEnvVars ( ) . then ( r => setEnvVars ( r ) ) ;
88
+ }
89
+
90
+ useEffect ( ( ) => {
91
+ update ( )
92
+ } , [ ] ) ;
93
+
94
+
95
+ const add = ( ) => {
96
+ setCurrentEnvVar ( { name : '' , value : '' , repositoryPattern : '' } ) ;
97
+ setAddEnvVarModalVisible ( true ) ;
98
+ }
99
+
100
+ const edit = ( ev : UserEnvVarValue ) => {
101
+ setCurrentEnvVar ( ev ) ;
102
+ setAddEnvVarModalVisible ( true ) ;
103
+ }
104
+
105
+ const save = async ( variable : UserEnvVarValue ) => {
106
+ await getGitpodService ( ) . server . setEnvVar ( variable ) ;
107
+ await update ( ) ;
108
+ } ;
109
+
110
+ const deleteV = async ( variable : UserEnvVarValue ) => {
111
+ await getGitpodService ( ) . server . deleteEnvVar ( variable ) ;
112
+ await update ( ) ;
113
+ } ;
114
+
115
+ const validate = ( variable : UserEnvVarValue ) => {
116
+ const name = variable . name ;
117
+ const pattern = variable . repositoryPattern ;
118
+ if ( name . trim ( ) === '' ) {
119
+ return 'Name must not be empty.' ;
120
+ }
121
+ if ( ! / ^ [ a - z A - Z 0 - 9 _ ] * $ / . test ( name ) ) {
122
+ return 'Name must match /[a-zA-Z_]+[a-zA-Z0-9_]*/.' ;
123
+ }
124
+ if ( variable . value . trim ( ) === '' ) {
125
+ return 'Value must not be empty.' ;
126
+ }
127
+ if ( pattern . trim ( ) === '' ) {
128
+ return 'Scope must not be empty.' ;
129
+ }
130
+ const split = pattern . split ( '/' ) ;
131
+ if ( split . length < 2 ) {
132
+ return "A scope must use the form 'organization/repo'." ;
133
+ }
134
+ for ( const name of split ) {
135
+ if ( name !== '*' ) {
136
+ if ( ! / ^ [ a - z A - Z 0 - 9 _ \- . \* ] + $ / . test ( name ) ) {
137
+ return 'Invalid scope segment. Only ASCII characters, numbers, -, _, . or * are allowed.' ;
138
+ }
139
+ }
140
+ }
141
+ return '' ;
142
+ } ;
143
+
144
+ return < SettingsPage title = 'Variables' subtitle = 'Configure environment variables for all workspaces.' >
145
+ { isAddEnvVarModalVisible ? < AddEnvVarModal
146
+ save = { save }
147
+ envVar = { currentEnvVar }
148
+ validate = { validate }
149
+ onClose = { ( ) => setAddEnvVarModalVisible ( false ) } /> : null }
150
+ { envVars . length === 0
151
+ ? < div className = "bg-gray-100 rounded-xl w-full h-96" >
152
+ < div className = "pt-28 flex flex-col items-center w-96 m-auto" >
153
+ < h3 className = "text-center pb-3" > No Environment Variables</ h3 >
154
+ < div className = "text-center pb-6 text-gray-500" > In addition to user-specific environment variables you can also pass variables through a workspace creation URL. < a className = "text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" href = "https://www.gitpod.io/docs/env-vars/" > Learn more</ a > </ div >
155
+ < button onClick = { add } className = "font-medium" > New Environment Variable</ button >
156
+ </ div >
157
+ </ div >
158
+ : < div >
159
+ < div className = "flex justify-end mb-2" >
160
+ < button onClick = { add } className = "ml-2 font-medium" > New Environment Variable</ button >
161
+ </ div >
162
+ < div className = "flex flex-col space-y-2" >
163
+ < div className = "px-3 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-b border-gray-200" >
164
+ < div className = "w-5/12" > Name</ div >
165
+ < div className = "w-5/12" > Scope</ div >
166
+ < div className = "w-2/12" > </ div >
167
+ </ div >
168
+ </ div >
169
+ < div className = "flex flex-col" >
170
+ { envVars . map ( ev => {
171
+ return < div className = "rounded-xl whitespace-nowrap flex space-x-2 py-3 px-3 w-full justify-between hover:bg-gray-100 focus:bg-gitpod-kumquat-light group" >
172
+ < div className = "w-5/12 m-auto" > { ev . name } </ div >
173
+ < div className = "w-5/12 m-auto text-sm text-gray-400" > { ev . repositoryPattern } </ div >
174
+ < div className = "w-2/12 flex justify-end" >
175
+ < div className = "flex w-8 self-center hover:bg-gray-200 rounded-md cursor-pointer" >
176
+ < ContextMenu menuEntries = { [
177
+ {
178
+ title : 'Edit' ,
179
+ onClick : ( ) => edit ( ev ) ,
180
+ separator : true
181
+ } ,
182
+ {
183
+ title : 'Delete' ,
184
+ customFontStyle : 'text-red-600 hover:text-red-800' ,
185
+ onClick : ( ) => deleteV ( ev )
186
+ } ,
187
+ ] } >
188
+ < img className = "w-8 h-8 p-1" src = { ThreeDots } alt = "Actions" />
189
+ </ ContextMenu >
190
+ </ div >
191
+ </ div >
192
+ </ div >
193
+ } ) }
194
+ </ div >
195
+ </ div >
196
+ }
197
+ </ SettingsPage > ;
15
198
}
0 commit comments