Skip to content

Commit 07b1ddd

Browse files
committed
[dashboard] Ask user for confirmation before deleting an environment variable + validate duplicate name and scope
Fixes #3604 Fixes #3942
1 parent 7f5fe36 commit 07b1ddd

File tree

1 file changed

+53
-16
lines changed

1 file changed

+53
-16
lines changed

components/dashboard/src/settings/EnvironmentVariables.tsx

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,28 @@ function AddEnvVarModal(p: EnvVarModalProps) {
7979
</Modal>
8080
}
8181

82+
function DeleteEnvVarModal(p: { variable: UserEnvVarValue, deleteVariable: () => void, onClose: () => void }) {
83+
return <Modal visible={true} onClose={p.onClose}>
84+
<h3 className="mb-4">Delete Variable?</h3>
85+
<div className="border-t border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 py-4 flex flex-col">
86+
<div className="grid grid-cols-3 gap-4 px-3">
87+
<h4 className="truncate">Name</h4>
88+
<h4 className="truncate">Value</h4>
89+
<h4 className="truncate">Scope</h4>
90+
</div>
91+
<div className="grid grid-cols-3 gap-4 text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-xl p-3">
92+
<span className="truncate text-gray-900 dark:text-gray-50">{p.variable.name}</span>
93+
<span className="truncate">{p.variable.value}</span>
94+
<span className="truncate">{p.variable.repositoryPattern}</span>
95+
</div>
96+
</div>
97+
<div className="flex justify-end mt-6">
98+
<button className="secondary" onClick={p.onClose}>Cancel</button>
99+
<button className="ml-2 danger" onClick={() => { p.deleteVariable(); p.onClose(); }} >Delete Variable</button>
100+
</div>
101+
</Modal>;
102+
}
103+
82104
function sortEnvVars(a: UserEnvVarValue, b: UserEnvVarValue) {
83105
if (a.name === b.name) {
84106
return a.repositoryPattern > b.repositoryPattern ? 1 : -1;
@@ -90,6 +112,7 @@ export default function EnvVars() {
90112
const [envVars, setEnvVars] = useState([] as UserEnvVarValue[]);
91113
const [currentEnvVar, setCurrentEnvVar] = useState({ name: '', value: '', repositoryPattern: '' } as UserEnvVarValue);
92114
const [isAddEnvVarModalVisible, setAddEnvVarModalVisible] = useState(false);
115+
const [isDeleteEnvVarModalVisible, setDeleteEnvVarModalVisible] = useState(false);
93116
const update = async () => {
94117
await getGitpodService().server.getAllEnvVars().then(r => setEnvVars(r.sort(sortEnvVars)));
95118
}
@@ -102,19 +125,27 @@ export default function EnvVars() {
102125
const add = () => {
103126
setCurrentEnvVar({ name: '', value: '', repositoryPattern: '' });
104127
setAddEnvVarModalVisible(true);
128+
setDeleteEnvVarModalVisible(false);
105129
}
106130

107-
const edit = (ev: UserEnvVarValue) => {
108-
setCurrentEnvVar(ev);
131+
const edit = (variable: UserEnvVarValue) => {
132+
setCurrentEnvVar(variable);
109133
setAddEnvVarModalVisible(true);
134+
setDeleteEnvVarModalVisible(false);
135+
}
136+
137+
const confirmDeleteVariable = (variable: UserEnvVarValue) => {
138+
setCurrentEnvVar(variable);
139+
setAddEnvVarModalVisible(false);
140+
setDeleteEnvVarModalVisible(true);
110141
}
111142

112143
const save = async (variable: UserEnvVarValue) => {
113144
await getGitpodService().server.setEnvVar(variable);
114145
await update();
115146
};
116147

117-
const deleteV = async (variable: UserEnvVarValue) => {
148+
const deleteVariable = async (variable: UserEnvVarValue) => {
118149
await getGitpodService().server.deleteEnvVar(variable);
119150
await update();
120151
};
@@ -145,26 +176,32 @@ export default function EnvVars() {
145176
}
146177
}
147178
}
179+
if (!variable.id && envVars.some(v => v.name === name && v.repositoryPattern === pattern)) {
180+
return 'A variable with this name and scope already exists';
181+
}
148182
return '';
149183
};
150184

151-
return <PageWithSubMenu subMenu={settingsMenu} title='Variables' subtitle='Configure environment variables for all workspaces.'>
152-
{isAddEnvVarModalVisible ? <AddEnvVarModal
185+
return <PageWithSubMenu subMenu={settingsMenu} title='Variables' subtitle='Configure environment variables for all workspaces.'>
186+
{isAddEnvVarModalVisible && <AddEnvVarModal
153187
save={save}
154188
envVar={currentEnvVar}
155189
validate={validate}
156-
onClose={() => setAddEnvVarModalVisible(false)} /> : null}
190+
onClose={() => setAddEnvVarModalVisible(false)} />}
191+
{isDeleteEnvVarModalVisible && <DeleteEnvVarModal
192+
variable={currentEnvVar}
193+
deleteVariable={() => deleteVariable(currentEnvVar)}
194+
onClose={() => setDeleteEnvVarModalVisible(false)} />}
157195
<div className="flex items-start sm:justify-between mb-2">
158196
<div>
159197
<h3>Environment Variables</h3>
160198
<h2 className="text-gray-500">Variables are used to store information like passwords.</h2>
161199
</div>
162200
{envVars.length !== 0
163-
?
164-
<div className="mt-3 flex mt-0">
165-
<button onClick={add} className="ml-2">New Variable</button>
166-
</div>
167-
: null}
201+
? <div className="mt-3 flex mt-0">
202+
<button onClick={add} className="ml-2">New Variable</button>
203+
</div>
204+
: null}
168205
</div>
169206
{envVars.length === 0
170207
? <div className="bg-gray-100 dark:bg-gray-800 rounded-xl w-full h-96">
@@ -183,22 +220,22 @@ export default function EnvVars() {
183220
</div>
184221
</div>
185222
<div className="flex flex-col">
186-
{envVars.map(ev => {
223+
{envVars.map(variable => {
187224
return <div className="rounded-xl whitespace-nowrap flex space-x-2 py-3 px-3 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group">
188-
<div className="w-5/12 m-auto">{ev.name}</div>
189-
<div className="w-5/12 m-auto text-sm text-gray-400">{ev.repositoryPattern}</div>
225+
<div className="w-5/12 m-auto">{variable.name}</div>
226+
<div className="w-5/12 m-auto text-sm text-gray-400">{variable.repositoryPattern}</div>
190227
<div className="w-2/12 flex justify-end">
191228
<div className="flex w-8 self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer opacity-0 group-hover:opacity-100">
192229
<ContextMenu menuEntries={[
193230
{
194231
title: 'Edit',
195-
onClick: () => edit(ev),
232+
onClick: () => edit(variable),
196233
separator: true
197234
},
198235
{
199236
title: 'Delete',
200237
customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300',
201-
onClick: () => deleteV(ev)
238+
onClick: () => confirmDeleteVariable(variable)
202239
},
203240
]}>
204241
<svg className="w-8 h-8 p-1 text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Actions</title><g fill="currentColor" transform="rotate(90 12 12)"><circle cx="1" cy="1" r="2" transform="translate(5 11)"/><circle cx="1" cy="1" r="2" transform="translate(11 11)"/><circle cx="1" cy="1" r="2" transform="translate(17 11)"/></g></svg>

0 commit comments

Comments
 (0)