-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
EnvironmentVariables.tsx
241 lines (220 loc) · 9.96 KB
/
EnvironmentVariables.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/**
* Copyright (c) 2021 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/
import { UserEnvVarValue } from "@gitpod/gitpod-protocol";
import { useEffect, useRef, useState } from "react";
import ConfirmationModal from "../components/ConfirmationModal";
import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList";
import Modal from "../components/Modal";
import { PageWithSubMenu } from "../components/PageWithSubMenu";
import { getGitpodService } from "../service/service";
import settingsMenu from "./settings-menu";
import CodeText from "../components/CodeText";
interface EnvVarModalProps {
envVar: UserEnvVarValue;
onClose: () => void;
save: (v: UserEnvVarValue) => void;
validate: (v: UserEnvVarValue) => string;
}
function AddEnvVarModal(p: EnvVarModalProps) {
const [ev, setEv] = useState({...p.envVar});
const [error, setError] = useState('');
const ref = useRef(ev);
const update = (pev: Partial<UserEnvVarValue>) => {
const newEnv = { ...ref.current, ... pev};
setEv(newEnv);
ref.current = newEnv;
};
useEffect(() => {
setEv({...p.envVar});
setError('');
}, [p.envVar]);
const isNew = !p.envVar.id;
let save = () => {
const v = ref.current;
const errorMsg = p.validate(v);
if (errorMsg !== '') {
setError(errorMsg);
return false;
} else {
p.save(v);
p.onClose();
return true;
}
};
return <Modal visible={true} onClose={p.onClose} onEnter={save}>
<h3 className="mb-4">{isNew ? 'New' : 'Edit'} Variable</h3>
<div className="border-t border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 py-4 flex flex-col">
{error ? <div className="bg-gitpod-kumquat-light rounded-md p-3 text-gitpod-red text-sm mb-2">
{error}
</div> : null}
<div>
<h4>Name</h4>
<input autoFocus className="w-full" type="text" value={ev.name} onChange={(v) => { update({name: v.target.value}) }} />
</div>
<div className="mt-4">
<h4>Value</h4>
<input className="w-full" type="text" value={ev.value} onChange={(v) => { update({value: v.target.value}) }} />
</div>
<div className="mt-4">
<h4>Scope</h4>
<input className="w-full" type="text" value={ev.repositoryPattern} placeholder="e.g. owner/repository"
onChange={(v) => { update({repositoryPattern: v.target.value}) }} />
</div>
<div className="mt-1">
<p className="text-gray-500">You can pass a variable for a specific project or use wildcard character (<CodeText>*/*</CodeText>) to make it available in more projects.</p>
</div>
</div>
<div className="flex justify-end mt-6">
<button className="secondary" onClick={p.onClose}>Cancel</button>
<button className="ml-2" onClick={save} >{isNew ? 'Add' : 'Update'} Variable</button>
</div>
</Modal>
}
function DeleteEnvVarModal(p: { variable: UserEnvVarValue, deleteVariable: () => void, onClose: () => void }) {
return <ConfirmationModal
title="Delete Variable"
areYouSureText="Are you sure you want to delete this variable?"
buttonText="Delete Variable"
onClose={p.onClose}
onConfirm={() => { p.deleteVariable(); p.onClose(); }}
>
<div className="grid grid-cols-2 gap-4 px-3 text-sm text-gray-400">
<span className="truncate">Name</span>
<span className="truncate">Scope</span>
</div>
<div className="grid grid-cols-2 gap-4 p-3 mt-3 text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-xl">
<span className="truncate text-gray-900 dark:text-gray-50">{p.variable.name}</span>
<span className="truncate text-sm">{p.variable.repositoryPattern}</span>
</div>
</ConfirmationModal>;
}
function sortEnvVars(a: UserEnvVarValue, b: UserEnvVarValue) {
if (a.name === b.name) {
return a.repositoryPattern > b.repositoryPattern ? 1 : -1;
}
return a.name > b.name ? 1 : -1;
}
export default function EnvVars() {
const [envVars, setEnvVars] = useState([] as UserEnvVarValue[]);
const [currentEnvVar, setCurrentEnvVar] = useState({ name: '', value: '', repositoryPattern: '' } as UserEnvVarValue);
const [isAddEnvVarModalVisible, setAddEnvVarModalVisible] = useState(false);
const [isDeleteEnvVarModalVisible, setDeleteEnvVarModalVisible] = useState(false);
const update = async () => {
await getGitpodService().server.getAllEnvVars().then(r => setEnvVars(r.sort(sortEnvVars)));
}
useEffect(() => {
update()
}, []);
const add = () => {
setCurrentEnvVar({ name: '', value: '', repositoryPattern: '' });
setAddEnvVarModalVisible(true);
setDeleteEnvVarModalVisible(false);
}
const edit = (variable: UserEnvVarValue) => {
setCurrentEnvVar(variable);
setAddEnvVarModalVisible(true);
setDeleteEnvVarModalVisible(false);
}
const confirmDeleteVariable = (variable: UserEnvVarValue) => {
setCurrentEnvVar(variable);
setAddEnvVarModalVisible(false);
setDeleteEnvVarModalVisible(true);
}
const save = async (variable: UserEnvVarValue) => {
await getGitpodService().server.setEnvVar(variable);
await update();
};
const deleteVariable = async (variable: UserEnvVarValue) => {
await getGitpodService().server.deleteEnvVar(variable);
await update();
};
const validate = (variable: UserEnvVarValue) => {
const name = variable.name;
const pattern = variable.repositoryPattern;
if (name.trim() === '') {
return 'Name must not be empty.';
}
if (!/^[a-zA-Z0-9_]*$/.test(name)) {
return 'Name must match /[a-zA-Z_]+[a-zA-Z0-9_]*/.';
}
if (variable.value.trim() === '') {
return 'Value must not be empty.';
}
if (pattern.trim() === '') {
return 'Scope must not be empty.';
}
const split = pattern.split('/');
if (split.length < 2) {
return "A scope must use the form 'organization/repo'.";
}
for (const name of split) {
if (name !== '*') {
if (!/^[a-zA-Z0-9_\-.\*]+$/.test(name)) {
return 'Invalid scope segment. Only ASCII characters, numbers, -, _, . or * are allowed.';
}
}
}
if (!variable.id && envVars.some(v => v.name === name && v.repositoryPattern === pattern)) {
return 'A variable with this name and scope already exists';
}
return '';
};
return <PageWithSubMenu subMenu={settingsMenu} title='Variables' subtitle='Configure environment variables for all workspaces.'>
{isAddEnvVarModalVisible && <AddEnvVarModal
save={save}
envVar={currentEnvVar}
validate={validate}
onClose={() => setAddEnvVarModalVisible(false)} />}
{isDeleteEnvVarModalVisible && <DeleteEnvVarModal
variable={currentEnvVar}
deleteVariable={() => deleteVariable(currentEnvVar)}
onClose={() => setDeleteEnvVarModalVisible(false)} />}
<div className="flex items-start sm:justify-between mb-2">
<div>
<h3>Environment Variables</h3>
<h2 className="text-gray-500">Variables are used to store information like passwords.</h2>
</div>
{envVars.length !== 0
? <div className="mt-3 flex mt-0">
<button onClick={add} className="ml-2">New Variable</button>
</div>
: null}
</div>
{envVars.length === 0
? <div className="bg-gray-100 dark:bg-gray-800 rounded-xl w-full h-96">
<div className="pt-28 flex flex-col items-center w-96 m-auto">
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">No Environment Variables</h3>
<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="gp-link" href="https://www.gitpod.io/docs/environment-variables/#using-the-account-settings">Learn more</a></div>
<button onClick={add}>New Variable</button>
</div>
</div>
: <ItemsList>
<Item header={true}>
<ItemField className="w-5/12 my-auto">Name</ItemField>
<ItemField className="w-5/12 my-auto">Scope</ItemField>
</Item>
{envVars.map(variable => {
return <Item className="whitespace-nowrap">
<ItemField className="w-5/12 overflow-ellipsis truncate my-auto">{variable.name}</ItemField>
<ItemField className="w-5/12 overflow-ellipsis truncate text-sm text-gray-400 my-auto">{variable.repositoryPattern}</ItemField>
<ItemFieldContextMenu menuEntries={[
{
title: 'Edit',
onClick: () => edit(variable),
separator: true
},
{
title: 'Delete',
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
onClick: () => confirmDeleteVariable(variable)
},
]} />
</Item>
})}
</ItemsList>
}
</PageWithSubMenu>;
}