From ab844dbe1e60bae39ae7abc7f6845f564e02c53c Mon Sep 17 00:00:00 2001 From: sdess09 <37374498+sdess09@users.noreply.github.com> Date: Fri, 15 Sep 2023 16:08:26 -0400 Subject: [PATCH] Porter v2 env (#3582) Co-authored-by: Ian Edwards --- .../update_app_environment_group.go | 24 +- dashboard/package-lock.json | 14 +- dashboard/package.json | 4 +- dashboard/src/lib/hooks/useAppValidation.ts | 12 +- dashboard/src/lib/porter-apps/index.ts | 25 +- .../app-view/AppDataContainer.tsx | 2 +- .../app-dashboard/create-app/CreateApp.tsx | 106 ++++- .../app-settings/EnvVariables.tsx | 28 +- .../env-groups/EnvGroupArrayV2.tsx | 403 ++++++++++++++++++ dashboard/src/shared/api.tsx | 25 +- 10 files changed, 572 insertions(+), 71 deletions(-) create mode 100644 dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArrayV2.tsx diff --git a/api/server/handlers/porter_app/update_app_environment_group.go b/api/server/handlers/porter_app/update_app_environment_group.go index ffc7fc7fa8..46f5d272b9 100644 --- a/api/server/handlers/porter_app/update_app_environment_group.go +++ b/api/server/handlers/porter_app/update_app_environment_group.go @@ -45,18 +45,18 @@ func NewUpdateAppEnvironmentHandler( // UpdateAppEnvironmentRequest represents the accepted fields on a request to the /apps/{porter_app_name}/environment-group endpoint type UpdateAppEnvironmentRequest struct { - DeploymentTargetID string `schema:"deployment_target_id"` - Variables map[string]string `schema:"variables"` - Secrets map[string]string `schema:"secrets"` + DeploymentTargetID string `json:"deployment_target_id"` + Variables map[string]string `json:"variables"` + Secrets map[string]string `json:"secrets"` // HardUpdate is used to remove any variables that are not specified in the request. If false, the request will only update the variables specified in the request, // and leave all other variables untouched. - HardUpdate bool `schema:"remove_missing"` + HardUpdate bool `json:"remove_missing"` } // UpdateAppEnvironmentResponse represents the fields on the response object from the /apps/{porter_app_name}/environment-group endpoint type UpdateAppEnvironmentResponse struct { - EnvGroupName string `schema:"env_group_name"` - EnvGroupVersion int `schema:"env_group_version"` + EnvGroupName string `json:"env_group_name"` + EnvGroupVersion int `json:"env_group_version"` } // ServeHTTP updates or creates the environment group for an app @@ -81,6 +81,18 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) return } + porterApp, err := c.Config().Repo.PorterApp().ReadPorterAppByName(cluster.ID, appName) + if err != nil { + err := telemetry.Error(ctx, span, nil, "error getting porter app by name") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) + return + } + if porterApp.ID == 0 { + err := telemetry.Error(ctx, span, nil, "porter app not found") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound)) + return + } + telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-id", Value: porterApp.ID}) if request.DeploymentTargetID == "" { err := telemetry.Error(ctx, span, nil, "must provide deployment target id") diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 4d6ebc83f6..217b58da48 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -13,7 +13,7 @@ "@loadable/component": "^5.15.2", "@material-ui/core": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", - "@porter-dev/api-contracts": "^0.0.100", + "@porter-dev/api-contracts": "^0.1.4", "@react-spring/web": "^9.6.1", "@sentry/react": "^6.13.2", "@sentry/tracing": "^6.13.2", @@ -2454,9 +2454,9 @@ } }, "node_modules/@porter-dev/api-contracts": { - "version": "0.0.100", - "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.100.tgz", - "integrity": "sha512-Y17fzm6HHmClFnMEWgwr178wZBTOuF17903/2icG/u4CA9JhtVgH6QvSzYcJ/Eu0kX+7pXm6pw24bxagFIeivA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.1.4.tgz", + "integrity": "sha512-bLsjDmIxrLwZNWrPJFe7p0MC5BPVNTzwFMn8RIS4eniRIwfNWyi/MDZGq/RaWftvqEg8/G1KlI6ixCvRjoKrSg==", "dependencies": { "@bufbuild/protobuf": "^1.1.0" } @@ -16943,9 +16943,9 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@porter-dev/api-contracts": { - "version": "0.0.100", - "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.100.tgz", - "integrity": "sha512-Y17fzm6HHmClFnMEWgwr178wZBTOuF17903/2icG/u4CA9JhtVgH6QvSzYcJ/Eu0kX+7pXm6pw24bxagFIeivA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.1.4.tgz", + "integrity": "sha512-bLsjDmIxrLwZNWrPJFe7p0MC5BPVNTzwFMn8RIS4eniRIwfNWyi/MDZGq/RaWftvqEg8/G1KlI6ixCvRjoKrSg==", "requires": { "@bufbuild/protobuf": "^1.1.0" } diff --git a/dashboard/package.json b/dashboard/package.json index 8b999db188..008fa1ccd7 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -8,7 +8,7 @@ "@loadable/component": "^5.15.2", "@material-ui/core": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", - "@porter-dev/api-contracts": "^0.0.100", + "@porter-dev/api-contracts": "^0.1.4", "@react-spring/web": "^9.6.1", "@sentry/react": "^6.13.2", "@sentry/tracing": "^6.13.2", @@ -147,4 +147,4 @@ "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" } -} +} \ No newline at end of file diff --git a/dashboard/src/lib/hooks/useAppValidation.ts b/dashboard/src/lib/hooks/useAppValidation.ts index b63571aa98..821cacec94 100644 --- a/dashboard/src/lib/hooks/useAppValidation.ts +++ b/dashboard/src/lib/hooks/useAppValidation.ts @@ -70,12 +70,16 @@ export const useAppValidation = ({ if (!deploymentTargetID) { throw new Error("No deployment target selected"); } - + const variables = data.app.env + .filter((e) => !e.deleted) + .reduce((acc: Record, curr) => { + acc[curr.key] = curr.value; + return acc; + }, {}); const envVariableDeletions = removedEnvKeys( - data.app.env, + variables, prevRevision?.env || {} ); - const proto = clientAppToProto(data); const commit_sha = await match(data.source) .with({ type: "github" }, async (src) => { @@ -125,7 +129,7 @@ export const useAppValidation = ({ atob(validAppData.validate_b64_app_proto) ); - return validatedAppProto; + return { validatedAppProto: validatedAppProto, env: data.app.env }; }, [deploymentTargetID, currentProject, currentCluster] ); diff --git a/dashboard/src/lib/porter-apps/index.ts b/dashboard/src/lib/porter-apps/index.ts index 8f78817df2..2d17bd87b7 100644 --- a/dashboard/src/lib/porter-apps/index.ts +++ b/dashboard/src/lib/porter-apps/index.ts @@ -70,9 +70,16 @@ export const clientAppValidator = z.object({ readOnly: z.boolean(), value: z.string(), }), + envGroups: z.object({ name: z.string(), version: z.bigint() }).array().default([]), services: serviceValidator.array(), predeploy: serviceValidator.array().optional(), - env: z.record(z.string(), z.string()).default({}), + env: z.object({ + key: z.string(), + value: z.string(), + hidden: z.boolean(), + locked: z.boolean(), + deleted: z.boolean(), + }).array().default([]), build: buildValidator, }); export type ClientPorterApp = z.infer; @@ -181,7 +188,10 @@ export function clientAppToProto(data: PorterAppFormData): PorterApp { new PorterApp({ name: app.name.value, services, - env: app.env, + envGroups: app.envGroups.map((eg) => ({ + name: eg.name, + version: eg.version, + })), build: clientBuildToProto(app.build), ...(predeploy && { predeploy: serviceProto(serializeService(predeploy)), @@ -194,7 +204,10 @@ export function clientAppToProto(data: PorterAppFormData): PorterApp { new PorterApp({ name: app.name.value, services, - env: app.env, + envGroups: app.envGroups.map((eg) => ({ + name: eg.name, + version: eg.version, + })), image: { repository: src.image.repository, tag: src.image.tag, @@ -295,7 +308,8 @@ export function clientAppFromProto( }, services, predeploy: predeployList, - env: proto.env, + env: [], + envGroups: proto.envGroups.map((eg) => ({ name: eg.name, version: eg.version })), build: clientBuildFromProto(proto.build) ?? { method: "pack", context: "./", @@ -326,7 +340,8 @@ export function clientAppFromProto( }, services, predeploy, - env: proto.env, + env: [], + envGroups: proto.envGroups.map((eg) => ({ name: eg.name, version: eg.version })), build: clientBuildFromProto(proto.build) ?? { method: "pack", context: "./", diff --git a/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx b/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx index 3b05fe44aa..5d68bc6eb8 100644 --- a/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx +++ b/dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx @@ -151,7 +151,7 @@ const AppDataContainer: React.FC = ({ tabParam }) => { const onSubmit = handleSubmit(async (data) => { try { - const validatedAppProto = await validateApp(data, latestProto); + const { validatedAppProto } = await validateApp(data, latestProto); await api.applyApp( "", { diff --git a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx index 32c6501fb5..1052ec0f1d 100644 --- a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx +++ b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx @@ -35,7 +35,7 @@ import EnvVariables from "../validate-apply/app-settings/EnvVariables"; import { usePorterYaml } from "lib/hooks/usePorterYaml"; import { valueExists } from "shared/util"; import api from "shared/api"; -import { PorterApp } from "@porter-dev/api-contracts"; +import { EnvGroup, PorterApp } from "@porter-dev/api-contracts"; import GithubActionModal from "../new-app-flow/GithubActionModal"; import { useDefaultDeploymentTarget } from "lib/hooks/useDeploymentTarget"; import Error from "components/porter/Error"; @@ -44,6 +44,7 @@ import { useAppValidation } from "lib/hooks/useAppValidation"; import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; import PorterYamlModal from "./PorterYamlModal"; +import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray"; type CreateAppProps = {} & RouteComponentProps; @@ -55,7 +56,10 @@ const CreateApp: React.FC = ({ history }) => { count: number; }>({ detected: false, count: 0 }); const [showGHAModal, setShowGHAModal] = React.useState(false); - const [userHasSeenNoPorterYamlFoundModal, setUserHasSeenNoPorterYamlFoundModal] = React.useState(false); + const [ + userHasSeenNoPorterYamlFoundModal, + setUserHasSeenNoPorterYamlFoundModal, + ] = React.useState(false); const [ validatedAppProto, @@ -135,7 +139,12 @@ const CreateApp: React.FC = ({ history }) => { const build = watch("app.build"); const image = watch("source.image"); const services = watch("app.services"); - const { detectedServices: servicesFromYaml, porterYamlFound, detectedName, loading: isLoadingPorterYaml } = usePorterYaml({ source: source?.type === "github" ? source : null }); + const { + detectedServices: servicesFromYaml, + porterYamlFound, + detectedName, + loading: isLoadingPorterYaml, + } = usePorterYaml({ source: source?.type === "github" ? source : null }); const deploymentTarget = useDefaultDeploymentTarget(); const { updateAppStep } = useAppAnalytics(name.value); const { validateApp } = useAppValidation({ @@ -146,7 +155,7 @@ const CreateApp: React.FC = ({ history }) => { const onSubmit = handleSubmit(async (data) => { try { setDeployError(""); - const validatedAppProto = await validateApp(data); + const { validatedAppProto, env } = await validateApp(data); setValidatedAppProto(validatedAppProto); if (source.type === "github") { @@ -154,7 +163,7 @@ const CreateApp: React.FC = ({ history }) => { return; } - await createAndApply({ app: validatedAppProto, source }); + await createAndApply({ app: validatedAppProto, source, env }); } catch (err) { if (axios.isAxiosError(err) && err.response?.data?.error) { setDeployError(err.response?.data?.error); @@ -170,9 +179,11 @@ const CreateApp: React.FC = ({ history }) => { async ({ app, source, + env, }: { app: PorterApp | null; source: SourceOptions; + env: KeyValueType[]; }) => { setIsDeploying(true); // log analytics event that we started form submission @@ -199,10 +210,57 @@ const CreateApp: React.FC = ({ history }) => { } ); + const variables = env + .filter((e) => !e.hidden && !e.deleted) + .reduce((acc: Record, item) => { + acc[item.key] = item.value; + return acc; + }, {}); + const secrets = env + .filter((e) => !e.deleted) + .reduce((acc: Record, item) => { + if (item.hidden) { + acc[item.key] = item.value; + } + return acc; + }, {}); + const envGroupResponse = await api.updateEnvironmentGroupV2( + "", + { + deployment_target_id: deploymentTarget.deployment_target_id, + variables: variables, + secrets: secrets, + }, + { + id: currentProject.id, + cluster_id: currentCluster.id, + app_name: app.name, + } + ); + + const addedEnvGroup = await z + .object({ + env_group_name: z.string(), + env_group_version: z.number(), + }) + .parseAsync(envGroupResponse.data); + const envGroups = [ + ...app.envGroups, + { + name: addedEnvGroup.env_group_name, + version: addedEnvGroup.env_group_version, + }, + ]; + + const appWithSeededEnv = new PorterApp({ + ...app, + envGroups, + }); + await api.applyApp( "", { - b64_app_proto: btoa(app.toJsonString()), + b64_app_proto: btoa(appWithSeededEnv.toJsonString()), deployment_target_id: deploymentTarget.deployment_target_id, }, { @@ -440,21 +498,27 @@ const CreateApp: React.FC = ({ history }) => { source={source} projectId={currentProject.id} /> - {!userHasSeenNoPorterYamlFoundModal && !porterYamlFound && !isLoadingPorterYaml && - ( - setUserHasSeenNoPorterYamlFoundModal(true)} - setPorterYamlPath={(porterYamlPath) => { - onChange(porterYamlPath); - }} - porterYamlPath={value} - /> - )} - /> - } + {!userHasSeenNoPorterYamlFoundModal && + !porterYamlFound && + !isLoadingPorterYaml && ( + ( + + setUserHasSeenNoPorterYamlFoundModal( + true + ) + } + setPorterYamlPath={(porterYamlPath) => { + onChange(porterYamlPath); + }} + porterYamlPath={value} + /> + )} + /> + )} ) : ( diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx index de9296cd3c..d8e026fcb4 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx @@ -1,36 +1,21 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useEffect } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { PorterAppFormData } from "lib/porter-apps"; -import EnvGroupArrayStacks, { - KeyValueType, -} from "main/home/cluster-dashboard/env-groups/EnvGroupArrayStacks"; - +import EnvGroupArrayV2 from "main/home/cluster-dashboard/env-groups/EnvGroupArrayV2"; +import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArrayV2"; const EnvVariables: React.FC = () => { const { control } = useFormContext(); - const recordToKVType = useCallback((env?: Record) => { - return Object.entries(env ?? []).map(([key, value]) => { - return { key, value, hidden: false, locked: false, deleted: false }; - }); - }, []); - - const kvTypeToRecord = useCallback((env: KeyValueType[]) => { - return env.reduce((acc, { key, value }) => { - acc[key] = value; - return acc; - }, {} as Record); - }, []); - return ( ( - { - onChange(kvTypeToRecord(x)); + onChange(x); }} fileUpload={true} syncedEnvGroups={[]} @@ -40,4 +25,5 @@ const EnvVariables: React.FC = () => { ); }; + export default EnvVariables; diff --git a/dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArrayV2.tsx b/dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArrayV2.tsx new file mode 100644 index 0000000000..ef5069777e --- /dev/null +++ b/dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArrayV2.tsx @@ -0,0 +1,403 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import Modal from "main/home/modals/Modal"; +import EnvEditorModal from "main/home/modals/EnvEditorModal"; + +import upload from "assets/upload.svg"; +import { MultiLineInput } from "components/porter-form/field-components/KeyValueArray"; +import { dotenv_parse } from "shared/string_utils"; +import { NewPopulatedEnvGroup, PopulatedEnvGroup } from "components/porter-form/types"; +import Text from "components/porter/Text"; +import Spacer from "components/porter/Spacer"; +export type KeyValueType = { + key: string; + value: string; + hidden: boolean; + locked: boolean; + deleted: boolean; +}; + +type PropsType = { + label?: string; + values: KeyValueType[]; + setValues: (x: KeyValueType[]) => void; + disabled?: boolean; + fileUpload?: boolean; + secretOption?: boolean; + syncedEnvGroups?: NewPopulatedEnvGroup[]; +}; + +const EnvGroupArrayV2 = ({ + label, + values, + setValues, + disabled, + fileUpload, + secretOption, + syncedEnvGroups +}: PropsType) => { + const [showEditorModal, setShowEditorModal] = useState(false); + + useEffect(() => { + if (!values) { + setValues([]); + } + }, [values]); + const isKeyOverriding = (key: string) => { + if (!syncedEnvGroups) return false; + return syncedEnvGroups.some(envGroup => + key in envGroup.variables || key in envGroup?.secret_variables + ); + }; + + const readFile = (env: string) => { + const envObj = dotenv_parse(env); + const _values = [...values]; + + for (const key in envObj) { + let push = true; + + for (let i = 0; i < values.length; i++) { + const existingKey = values[i]["key"]; + const isExistingKeyDeleted = values[i]["deleted"]; + if (key === existingKey && !isExistingKeyDeleted) { + _values[i]["value"] = envObj[key]; + push = false; + } + } + + if (push) { + _values.push({ + key, + value: envObj[key], + hidden: false, + locked: false, + deleted: false, + }); + } + } + + setValues(_values); + }; + + if (!values) { + return null; + } + + return ( + <> + + + {!!values?.length && + values.map((entry: KeyValueType, i: number) => { + if (!entry.deleted) { + return ( + + { + const _values = [...values]; + _values[i].key = e.target.value; + setValues(_values); + }} + disabled={disabled || entry.locked} + spellCheck={false} + override={isKeyOverriding(entry.key)} + /> + < Spacer x={.5} inline /> + {entry.hidden ? ( + entry.value?.includes("PORTERSECRET") ? ( + ) : ( + { + const _values = [...values]; + _values[i].value = e.target.value; + setValues(_values); + }} + disabled={disabled || entry.locked} + type={entry.hidden ? "password" : "text"} + spellCheck={false} + override={isKeyOverriding(entry.key)} + + />) + ) : ( + entry.value?.includes("PORTERSECRET") ? ( + ) : ( + { + const _values = [...values]; + _values[i].value = e.target.value; + setValues(_values); + }} + rows={entry.value?.split("\n").length} + disabled={disabled || entry.locked} + spellCheck={false} + override={isKeyOverriding(entry.key)} + /> + )) + } + {( + { + if (!entry.locked) { + const values1 = [...values]; + values1[i].hidden = !values1[i].hidden; + setValues(values1); + } + }} + disabled={entry.locked} + > + {entry.hidden ? ( + lock + ) : ( + lock_open + )} + + )} + + {!disabled && ( + { + setValues(values.filter((val, index) => index !== i)); + }} + > + cancel + + )} + + {isKeyOverriding(entry.key) && <> Key is overriding value in a environment group} + + ); + } + })} + {!disabled && ( + + { + const _values = [ + ...values, + { + key: "", + value: "", + hidden: false, + locked: false, + deleted: false, + }, + ]; + setValues(_values); + }} + > + add Add Row + + + {fileUpload && ( + { + setShowEditorModal(true); + }} + > + Copy from File + + )} + + )} + + {showEditorModal && ( + setShowEditorModal(false)} + width="60%" + height="650px" + > + setShowEditorModal(false)} + setEnvVariables={(envFile: string) => readFile(envFile)} + /> + + )} + + ); +}; + +export default EnvGroupArrayV2; + + +const AddRowButton = styled.div` + display: flex; + align-items: center; + width: 270px; + font-size: 13px; + color: #aaaabb; + height: 32px; + border-radius: 3px; + cursor: pointer; + background: #ffffff11; + :hover { + background: #ffffff22; + } + + > i { + color: #ffffff44; + font-size: 16px; + margin-left: 8px; + margin-right: 10px; + display: flex; + align-items: center; + justify-content: center; + } +`; + +const UploadButton = styled(AddRowButton)` + background: none; + position: relative; + border: 1px solid #ffffff55; + > i { + color: #ffffff44; + font-size: 16px; + margin-left: 8px; + margin-right: 10px; + display: flex; + align-items: center; + justify-content: center; + } + > img { + width: 14px; + margin-left: 10px; + margin-right: 12px; + } +`; + +const DeleteButton = styled.div` + width: 15px; + height: 15px; + display: flex; + align-items: center; + margin-left: 8px; + margin-top: -3px; + justify-content: center; + + > i { + font-size: 17px; + color: #ffffff44; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + :hover { + color: #ffffff88; + } + } +`; + +const HideButton = styled(DeleteButton)` + margin-top: -5px; + > i { + font-size: 19px; + cursor: ${(props: { disabled: boolean }) => + props.disabled ? "default" : "pointer"}; + :hover { + color: ${(props: { disabled: boolean }) => + props.disabled ? "#ffffff44" : "#ffffff88"}; + } + } +`; + +const InputWrapper = styled.div` + display: flex; + align-items: center; + margin-top: 5px; + +`; + +type InputProps = { + disabled?: boolean; + width: string; + override?: boolean; +}; + +const Input = styled.input` + outline: none; + border: none; + margin-bottom: 5px; + font-size: 13px; + background: #ffffff11; + border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')}; + border-radius: 3px; + width: ${(props) => props.width ? props.width : "270px"}; + color: ${(props) => props.disabled ? "#ffffff44" : "white"}; + padding: 5px 10px; + height: 35px; +`; +const Label = styled.div` + color: #ffffff; + margin-bottom: 10px; +`; + +const StyledInputArray = styled.div` + margin-bottom: 15px; + margin-top: 22px; +`; + +export const MultiLineInputer = styled.textarea` + outline: none; + border: none; + margin-bottom: 5px; + font-size: 13px; + background: #ffffff11; + border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')}; + border-radius: 3px; + min-width: ${(props) => (props.width ? props.width : "270px")}; + max-width: ${(props) => (props.width ? props.width : "270px")}; + color: ${(props) => (props.disabled ? "#ffffff44" : "white")}; + padding: 8px 10px 5px 10px; + min-height: 35px; + max-height: 100px; + white-space: nowrap; + + ::-webkit-scrollbar { + width: 8px; + :horizontal { + height: 8px; + } + } + + ::-webkit-scrollbar-corner { + width: 10px; + background: #ffffff11; + color: white; + } + + ::-webkit-scrollbar-track { + width: 10px; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + } + + ::-webkit-scrollbar-thumb { + background-color: darkgrey; + outline: 1px solid slategrey; + } +`; diff --git a/dashboard/src/shared/api.tsx b/dashboard/src/shared/api.tsx index 5bd74fd7cd..5893ce9c54 100644 --- a/dashboard/src/shared/api.tsx +++ b/dashboard/src/shared/api.tsx @@ -932,12 +932,12 @@ const listAppRevisions = baseApi< }); const getLatestAppRevisions = baseApi< -{},{ + {}, { project_id: number; cluster_id: number; -}>("GET", ({ project_id, cluster_id }) => { - return `/api/projects/${project_id}/clusters/${cluster_id}/apps/revisions`; -}) + }>("GET", ({ project_id, cluster_id }) => { + return `/api/projects/${project_id}/clusters/${cluster_id}/apps/revisions`; + }) const getGitlabProcfileContents = baseApi< { @@ -1794,6 +1794,22 @@ const getAllEnvGroups = baseApi< return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id}/environment-groups`; }); +const updateEnvironmentGroupV2 = baseApi< + { + deployment_target_id: string; + variables: Record; + secrets: Record; + remove_missing?: boolean; + }, + { + id: number; + cluster_id: number; + app_name: string; + } +>("POST", (pathParams) => { + return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id}/apps/${pathParams.app_name}/update-environment `; +}); + const listEnvGroups = baseApi< {}, { @@ -3108,6 +3124,7 @@ export default { updateStacksEnvGroup, listEnvGroups, getAllEnvGroups, + updateEnvironmentGroupV2, getEnvGroup, deleteEnvGroup, deleteNewEnvGroup,