Path: blob/main/components/dashboard/src/user-settings/EnvironmentVariables.tsx
2500 views
/**1* Copyright (c) 2021 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { UserEnvVar, UserEnvVarValue } from "@gitpod/gitpod-protocol";7import { useCallback, useEffect, useRef, useState } from "react";8import ConfirmationModal from "../components/ConfirmationModal";9import { Item, ItemField, ItemsList } from "../components/ItemsList";10import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal";11import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";12import { EnvironmentVariableEntry } from "./EnvironmentVariableEntry";13import { Heading2, Subheading } from "../components/typography/headings";14import { envVarClient } from "../service/public-api";15import { UserEnvironmentVariable } from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";16import { Button } from "@podkit/buttons/Button";17import { TextInputField } from "../components/forms/TextInputField";1819interface EnvVarModalProps {20envVar: UserEnvVarValue;21onClose: () => void;22save: (v: UserEnvVarValue) => void;23validate: (v: UserEnvVarValue) => string | undefined;24}2526function AddEnvVarModal(p: EnvVarModalProps) {27const [ev, setEv] = useState({ ...p.envVar });28const [error, setError] = useState("");29const ref = useRef(ev);3031const update = (pev: Partial<UserEnvVarValue>) => {32const newEnv = { ...ref.current, ...pev };33setEv(newEnv);34ref.current = newEnv;35};3637useEffect(() => {38setEv({ ...p.envVar });39setError("");40}, [p.envVar]);4142const isNew = !p.envVar.id;43let save = useCallback(async () => {44const v = ref.current;45const errorMsg = p.validate(v);46if (!!errorMsg) {47setError(errorMsg);48} else {49await p.save(v);50p.onClose();51}52}, [p]);5354return (55<Modal visible={true} onClose={p.onClose} onSubmit={save}>56<ModalHeader>{isNew ? "New" : "Edit"} Variable</ModalHeader>57<ModalBody>58{error ? (59<div className="bg-kumquat-light rounded-md p-3 text-gitpod-red text-sm mb-2">{error}</div>60) : null}61<TextInputField62label="Name"63value={ev.name}64type="text"65autoFocus66onChange={(val) => update({ name: val })}67/>6869<TextInputField label="Value" value={ev.value} type="text" onChange={(val) => update({ value: val })} />7071<TextInputField72label="Scope"73hint={74<>75You can pass a variable for a specific project or use wildcard character (<code>*/*</code>)76to make it available in more projects.77</>78}79value={ev.repositoryPattern}80type="text"81placeholder="e.g. owner/repository"82onChange={(val) => update({ repositoryPattern: val })}83/>84</ModalBody>85<ModalFooter>86<Button variant="secondary" onClick={p.onClose}>87Cancel88</Button>89<Button type="submit">{isNew ? "Add" : "Update"} Variable</Button>90</ModalFooter>91</Modal>92);93}9495function DeleteEnvVarModal(p: { variable: UserEnvVarValue; deleteVariable: () => void; onClose: () => void }) {96return (97<ConfirmationModal98title="Delete Variable"99areYouSureText="Are you sure you want to delete this variable?"100buttonText="Delete Variable"101onClose={p.onClose}102onConfirm={async () => {103await p.deleteVariable();104p.onClose();105}}106>107<div className="grid grid-cols-2 gap-4 px-3 text-sm text-gray-400">108<span className="truncate">Name</span>109<span className="truncate">Scope</span>110</div>111<div className="grid grid-cols-2 gap-4 p-3 mt-3 text-gray-400 bg-pk-surface-secondary rounded-xl">112<span className="truncate text-gray-900 dark:text-gray-50">{p.variable.name}</span>113<span className="truncate text-sm">{p.variable.repositoryPattern}</span>114</div>115</ConfirmationModal>116);117}118119function sortEnvVars(a: UserEnvironmentVariable, b: UserEnvironmentVariable) {120if (a.name === b.name) {121return a.repositoryPattern > b.repositoryPattern ? 1 : -1;122}123return a.name > b.name ? 1 : -1;124}125126export default function EnvVars() {127const [envVars, setEnvVars] = useState([] as UserEnvVarValue[]);128const [currentEnvVar, setCurrentEnvVar] = useState({129id: undefined,130name: "",131value: "",132repositoryPattern: "",133} as UserEnvVarValue);134const [isAddEnvVarModalVisible, setAddEnvVarModalVisible] = useState(false);135const [isDeleteEnvVarModalVisible, setDeleteEnvVarModalVisible] = useState(false);136const update = async () => {137await envVarClient.listUserEnvironmentVariables({}).then((r) =>138setEnvVars(139r.environmentVariables.sort(sortEnvVars).map((e) => ({140id: e.id,141name: e.name,142value: e.value,143repositoryPattern: e.repositoryPattern,144})),145),146);147};148149useEffect(() => {150update();151}, []);152153const add = () => {154setCurrentEnvVar({ id: undefined, name: "", value: "", repositoryPattern: "" });155setAddEnvVarModalVisible(true);156setDeleteEnvVarModalVisible(false);157};158159const edit = (variable: UserEnvVarValue) => {160setCurrentEnvVar(variable);161setAddEnvVarModalVisible(true);162setDeleteEnvVarModalVisible(false);163};164165const confirmDeleteVariable = (variable: UserEnvVarValue) => {166setCurrentEnvVar(variable);167setAddEnvVarModalVisible(false);168setDeleteEnvVarModalVisible(true);169};170171const save = async (variable: UserEnvVarValue) => {172if (variable.id) {173await envVarClient.updateUserEnvironmentVariable({174environmentVariableId: variable.id,175name: variable.name,176value: variable.value,177repositoryPattern: variable.repositoryPattern,178});179} else {180await envVarClient.createUserEnvironmentVariable({181name: variable.name,182value: variable.value,183repositoryPattern: variable.repositoryPattern,184});185}186187await update();188};189190const deleteVariable = async (variable: UserEnvVarValue) => {191await envVarClient.deleteUserEnvironmentVariable({192environmentVariableId: variable.id,193});194await update();195};196197const validate = (variable: UserEnvVarValue): string | undefined => {198const name = variable.name;199const pattern = variable.repositoryPattern;200const validationError = UserEnvVar.validate(variable);201if (validationError) {202return validationError;203}204if (!variable.id && envVars.some((v) => v.name === name && v.repositoryPattern === pattern)) {205return "A variable with this name and scope already exists";206}207return undefined;208};209210return (211<PageWithSettingsSubMenu>212{isAddEnvVarModalVisible && (213<AddEnvVarModal214save={save}215envVar={currentEnvVar}216validate={validate}217onClose={() => setAddEnvVarModalVisible(false)}218/>219)}220{isDeleteEnvVarModalVisible && (221<DeleteEnvVarModal222variable={currentEnvVar}223deleteVariable={async () => await deleteVariable(currentEnvVar)}224onClose={() => setDeleteEnvVarModalVisible(false)}225/>226)}227<div className="flex items-start sm:justify-between mb-2">228<div>229<Heading2>Environment Variables</Heading2>230<Subheading>231Variables are used to store information like passwords.{" "}232<a233className="gp-link"234href="https://www.gitpod.io/docs/configure/projects/environment-variables#environment-variables"235target="_blank"236rel="noreferrer"237>238Learn more239</a>240</Subheading>241</div>242{envVars.length !== 0 ? (243<div className="flex mt-0">244<Button onClick={add} className="ml-2">245New Variable246</Button>247</div>248) : null}249</div>250{envVars.length === 0 ? (251<div className="bg-pk-surface-secondary rounded-xl w-full h-96">252<div className="pt-28 flex flex-col items-center w-96 m-auto">253<Heading2 className="text-pk-content-invert-secondary text-center pb-3">254No Environment Variables255</Heading2>256<Subheading className="text-center pb-6">257In addition to user-specific environment variables you can also pass variables through a258workspace creation URL.259</Subheading>260<Button onClick={add}>New Variable</Button>261</div>262</div>263) : (264<ItemsList>265<Item header={true}>266<ItemField className="w-5/12 my-auto">Name</ItemField>267<ItemField className="w-5/12 my-auto">Scope</ItemField>268</Item>269{envVars.map((variable) => (270<EnvironmentVariableEntry271key={variable.id}272variable={variable}273edit={edit}274confirmDeleteVariable={confirmDeleteVariable}275/>276))}277</ItemsList>278)}279</PageWithSettingsSubMenu>280);281}282283284