From e4974b455ead447ef595866dab93ba53aa7266ab Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Fri, 26 Mar 2021 10:40:58 +0000 Subject: [PATCH] [dashboard] env variables --- components/dashboard/src/index.css | 2 +- .../src/settings/EnvironmentVariables.tsx | 193 +++++++++++++++++- .../dashboard/src/settings/SettingsPage.tsx | 2 +- components/dashboard/src/tailwind.output.css | 11 +- 4 files changed, 200 insertions(+), 8 deletions(-) diff --git a/components/dashboard/src/index.css b/components/dashboard/src/index.css index 647670e3632fd2..16d1d59461dfd6 100644 --- a/components/dashboard/src/index.css +++ b/components/dashboard/src/index.css @@ -43,7 +43,7 @@ } input[type=text] { - @apply text-xs block w-56 text-sm text-gray-600 rounded-md focus:border-gray-300 focus:bg-white focus:ring-0; + @apply text-xs block w-56 text-sm text-gray-600 rounded-md border-2 border-gray-400 focus:border-gray-500 focus:bg-white focus:ring-0; } input[type=text]::placeholder { diff --git a/components/dashboard/src/settings/EnvironmentVariables.tsx b/components/dashboard/src/settings/EnvironmentVariables.tsx index 2db21372d46e7d..25625e29a8be8e 100644 --- a/components/dashboard/src/settings/EnvironmentVariables.tsx +++ b/components/dashboard/src/settings/EnvironmentVariables.tsx @@ -4,12 +4,195 @@ * See License-AGPL.txt in the project root for license information. */ +import { UserEnvVarValue } from "@gitpod/gitpod-protocol"; +import { useEffect, useRef, useState } from "react"; +import ContextMenu from "../components/ContextMenu"; +import Modal from "../components/Modal"; +import { getGitpodService } from "../service/service"; import { SettingsPage } from "./SettingsPage"; +import ThreeDots from '../icons/ThreeDots.svg'; + +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) => { + 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 +

{isNew ? 'New' : 'Edit'} Variable

+
+ {error ?
+ {error} +
: null} +
+

Name

+ { update({name: v.target.value}) }} /> +
+
+

Value

+ { update({value: v.target.value}) }} /> +
+
+

Scope

+ { update({repositoryPattern: v.target.value}) }} /> +
+
+

You can pass a variable for a specific project or use wildcard characters (*/*) to make it available in more projects.

+
+
+
+ + +
+
+} export default function EnvVars() { - return
- -

Environment Variables

-
-
; + const [envVars, setEnvVars] = useState([] as UserEnvVarValue[]); + const [currentEnvVar, setCurrentEnvVar] = useState({ name: '', value: '', repositoryPattern: '' } as UserEnvVarValue); + const [isAddEnvVarModalVisible, setAddEnvVarModalVisible] = useState(false); + const update = async () => { + await getGitpodService().server.getEnvVars().then(r => setEnvVars(r)); + } + + useEffect(() => { + update() + }, []); + + + const add = () => { + setCurrentEnvVar({ name: '', value: '', repositoryPattern: '' }); + setAddEnvVarModalVisible(true); + } + + const edit = (ev: UserEnvVarValue) => { + setCurrentEnvVar(ev); + setAddEnvVarModalVisible(true); + } + + const save = async (variable: UserEnvVarValue) => { + await getGitpodService().server.setEnvVar(variable); + await update(); + }; + + const deleteV = 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.'; + } + } + } + return ''; + }; + + return + {isAddEnvVarModalVisible ? setAddEnvVarModalVisible(false)} /> : null} + {envVars.length === 0 + ?
+
+

No Environment Variables

+
In addition to user-specific environment variables you can also pass variables through a workspace creation URL. Learn more
+ +
+
+ :
+
+ +
+
+
+
Name
+
Scope
+
+
+
+
+ {envVars.map(ev => { + return
+
{ev.name}
+
{ev.repositoryPattern}
+
+
+ edit(ev), + separator: true + }, + { + title: 'Delete', + customFontStyle: 'text-red-600 hover:text-red-800', + onClick: () => deleteV(ev) + }, + ]}> + Actions + +
+
+
+ })} +
+
+ } +
; } \ No newline at end of file diff --git a/components/dashboard/src/settings/SettingsPage.tsx b/components/dashboard/src/settings/SettingsPage.tsx index 685dc920fbac56..b16ffcef78280c 100644 --- a/components/dashboard/src/settings/SettingsPage.tsx +++ b/components/dashboard/src/settings/SettingsPage.tsx @@ -17,7 +17,7 @@ export interface Props { export function SettingsPage(p: Props) { const location = useLocation(); - return
+ return
diff --git a/components/dashboard/src/tailwind.output.css b/components/dashboard/src/tailwind.output.css index 302ebe302238cb..ac6c0653d1b3ba 100644 --- a/components/dashboard/src/tailwind.output.css +++ b/components/dashboard/src/tailwind.output.css @@ -917,12 +917,21 @@ button:disabled { input[type=text]:focus { --tw-bg-opacity: 1; background-color: rgba(255, 255, 255, var(--tw-bg-opacity)); +} + +input[type=text] { --tw-border-opacity: 1; - border-color: rgba(214, 211, 209, var(--tw-border-opacity)); + border-color: rgba(168, 162, 158, var(--tw-border-opacity)); +} + +input[type=text]:focus { + --tw-border-opacity: 1; + border-color: rgba(120, 113, 108, var(--tw-border-opacity)); } input[type=text] { border-radius: 0.375rem; + border-width: 2px; display: block; font-size: 0.75rem; line-height: 1rem;