Parcourir la source

fix overflow and consolidate (#4361)

jusrhee il y a 2 ans
Parent
commit
d79ceb0543

+ 13 - 4
dashboard/src/components/porter/Expandable.tsx

@@ -2,6 +2,7 @@ import React, { useState } from "react";
 import styled from "styled-components";
 
 type Props = {
+  maxHeight?: string;
   header: React.ReactNode;
   children: React.ReactNode;
   style?: React.CSSProperties;
@@ -11,6 +12,7 @@ type Props = {
 
 // TODO: support footer for consolidation w/ app services
 const Expandable: React.FC<Props> = ({
+  maxHeight,
   header,
   children,
   style,
@@ -49,17 +51,24 @@ const Expandable: React.FC<Props> = ({
         <span className="material-icons dropdown">arrow_drop_down</span>
         <FullWidth>{header}</FullWidth>
       </Header>
-      <ExpandedContents isExpanded={isExpanded}>{children}</ExpandedContents>
+      <ExpandedContents
+        isExpanded={isExpanded}
+        maxHeight={maxHeight || "500px"}
+      >
+        {children}
+      </ExpandedContents>
     </StyledExpandable>
   );
 };
 
 export default Expandable;
 
-const ExpandedContents = styled.div<{ isExpanded: boolean }>`
+const ExpandedContents = styled.div<{
+  isExpanded: boolean;
+  maxHeight?: string;
+}>`
   transition: all 0.5s;
-  overflow: hidden;
-  max-height: ${({ isExpanded }) => (isExpanded ? "500px" : "0")};
+  max-height: ${({ isExpanded, maxHeight }) => (isExpanded ? maxHeight : "0")};
   padding: ${({ isExpanded }) => (isExpanded ? "20px" : "0")};
   border-bottom-left-radius: 5px;
   border-bottom-right-radius: 5px;

+ 159 - 169
dashboard/src/main/home/app-dashboard/expanded-app/env-vars/EnvGroupModal.tsx

@@ -1,33 +1,35 @@
-import { type RouteComponentProps, withRouter } from "react-router";
-import styled, { css } from "styled-components";
 import React, { useContext, useEffect, useState } from "react";
-import Loading from "components/Loading";
+import { isEmpty, isObject } from "lodash";
+import AceEditor from "react-ace";
+import { withRouter, type RouteComponentProps } from "react-router";
+import styled, { css } from "styled-components";
+import { set } from "zod";
 
+import Loading from "components/Loading";
+import {
+  type NewPopulatedEnvGroup,
+  type PartialEnvGroup,
+  type PopulatedEnvGroup,
+} from "components/porter-form/types";
+import Button from "components/porter/Button";
+import Checkbox from "components/porter/Checkbox";
+import Container from "components/porter/Container";
+import Error from "components/porter/Error";
 import Modal from "components/porter/Modal";
-import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
-import Button from "components/porter/Button";
-import api from "shared/api";
-import { getGithubAction } from "./utils";
-import AceEditor from "react-ace";
+import Text from "components/porter/Text";
 import YamlEditor from "components/YamlEditor";
-import Error from "components/porter/Error";
-import Container from "components/porter/Container";
-import Checkbox from "components/porter/Checkbox";
-import { Context } from "../../../../../shared/Context";
+
+import api from "shared/api";
 import sliders from "assets/sliders.svg";
-import { isEmpty, isObject } from "lodash";
+
+import { Context } from "../../../../../shared/Context";
 import {
   EnvGroupData,
   formattedEnvironmentValue,
 } from "../../../cluster-dashboard/env-groups/EnvGroup";
-import {
-  type PartialEnvGroup,
-  type PopulatedEnvGroup,
-  type NewPopulatedEnvGroup,
-} from "components/porter-form/types";
 import { type KeyValueType } from "../../../cluster-dashboard/env-groups/EnvGroupArray";
-import { set } from "zod";
+import { getGithubAction } from "./utils";
 
 type Props = RouteComponentProps & {
   closeModal: () => void;
@@ -38,7 +40,7 @@ type Props = RouteComponentProps & {
   setSyncedEnvGroups: (values: NewPopulatedEnvGroup[]) => void;
   namespace: string;
   newApp?: boolean;
-}
+};
 
 const EnvGroupModal: React.FC<Props> = ({
   closeModal,
@@ -51,11 +53,12 @@ const EnvGroupModal: React.FC<Props> = ({
   newApp,
 }) => {
   const { currentCluster, currentProject } = useContext(Context);
-  const [envGroups, setEnvGroups] = useState<any>([])
+  const [envGroups, setEnvGroups] = useState<any>([]);
   const [loading, setLoading] = useState<boolean>(true);
   const [error, setError] = useState<any>(null);
   const [shouldSync, setShouldSync] = useState<boolean>(true);
-  const [selectedEnvGroup, setSelectedEnvGroup] = useState<PopulatedEnvGroup | null>(null);
+  const [selectedEnvGroup, setSelectedEnvGroup] =
+    useState<PopulatedEnvGroup | null>(null);
   const [cloneSuccess, setCloneSuccess] = useState(false);
 
   const updateEnvGroups = async () => {
@@ -72,20 +75,16 @@ const EnvGroupModal: React.FC<Props> = ({
         )
         .then((res) => res.data?.environment_groups);
     } catch (error) {
-      setLoading(false)
+      setLoading(false);
       setError(true);
       return;
     }
 
-
-
     try {
-
-      setEnvGroups(populatedEnvGroups)
-      setLoading(false)
-
+      setEnvGroups(populatedEnvGroups);
+      setLoading(false);
     } catch (error) {
-      setLoading(false)
+      setLoading(false);
       setError(true);
     }
   };
@@ -97,7 +96,7 @@ const EnvGroupModal: React.FC<Props> = ({
   }, [values]);
 
   useEffect(() => {
-    setLoading(true)
+    setLoading(true);
     if (Array.isArray(availableEnvGroups)) {
       setEnvGroups(availableEnvGroups);
       setLoading(false);
@@ -114,23 +113,28 @@ const EnvGroupModal: React.FC<Props> = ({
         </LoadingWrapper>
       );
     } else {
-      const sortedEnvGroups = envGroups?.slice().sort((a, b) => a.name.localeCompare(b.name));
-
-      return sortedEnvGroups?.filter((envGroup) => {
-        if (!Array.isArray(syncedEnvGroups)) {
-          return true;
-        }
-        return !syncedEnvGroups?.find(
-          (syncedEnvGroup) => syncedEnvGroup?.name === envGroup?.name
-        );
-      })
+      const sortedEnvGroups = envGroups
+        ?.slice()
+        .sort((a, b) => a.name.localeCompare(b.name));
+
+      return sortedEnvGroups
+        ?.filter((envGroup) => {
+          if (!Array.isArray(syncedEnvGroups)) {
+            return true;
+          }
+          return !syncedEnvGroups?.find(
+            (syncedEnvGroup) => syncedEnvGroup?.name === envGroup?.name
+          );
+        })
         .map((envGroup: any, i: number) => {
           return (
             <EnvGroupRow
               key={i}
               isSelected={selectedEnvGroup === envGroup}
               lastItem={i === envGroups?.length - 1}
-              onClick={() => { setSelectedEnvGroup(envGroup); }}
+              onClick={() => {
+                setSelectedEnvGroup(envGroup);
+              }}
             >
               <img src={sliders} />
               {envGroup?.name}
@@ -142,24 +146,20 @@ const EnvGroupModal: React.FC<Props> = ({
 
   const onSubmit = () => {
     if (shouldSync) {
-
       syncedEnvGroups.push(selectedEnvGroup);
       setSyncedEnvGroups(syncedEnvGroups);
-    }
-    else {
+    } else {
       const _values = [...values];
 
-      Object.entries(selectedEnvGroup?.variables || {})
-        .map(
-          ([key, value]) =>
-            _values.push({
-              key,
-              value ,
-              hidden: false,
-              locked: false,
-              deleted: false,
-            })
-        )
+      Object.entries(selectedEnvGroup?.variables || {}).map(([key, value]) =>
+        _values.push({
+          key,
+          value,
+          hidden: false,
+          locked: false,
+          deleted: false,
+        })
+      );
       setValues(_values);
     }
     closeModal();
@@ -167,141 +167,131 @@ const EnvGroupModal: React.FC<Props> = ({
 
   return (
     <Modal closeModal={closeModal}>
-      <Text size={16}>
-        Load env group
-      </Text>
+      <Text size={16}>Load env group</Text>
       <Spacer height="15px" />
       <ColumnContainer>
-
         <ScrollableContainer>
-          {syncedEnvGroups?.length != envGroups?.length ? (<>
-            <Text color="helper">
-              Select an Env Group to load into your application.
-            </Text>
-            <Spacer y={0.5} />
-            <GroupModalSections>
-              <SidebarSection $expanded={!selectedEnvGroup}>
-                <EnvGroupList>{renderEnvGroupList()}</EnvGroupList>
-              </SidebarSection>
-              {selectedEnvGroup && (
-                <><SidebarSection>
-
-                  <GroupEnvPreview>
-                    {
-                      isObject(selectedEnvGroup?.variables) || isObject(selectedEnvGroup?.secret_variables) ? (
-                        <>
-                          {[
-                            ...Object.entries(selectedEnvGroup?.variables || {}).map(([key, value]) => ({
-                              source: 'variables',
-                              key,
-                              value,
-                            })),
-                            ...Object.entries(selectedEnvGroup?.secret_variables || {}).map(([key, value]) => ({
-                              source: 'secret_variables',
-                              key,
-                              value,
-                            })),
-                          ]
-                            .map(({ key, value, source }, index) => (
+          {syncedEnvGroups?.length != envGroups?.length ? (
+            <>
+              <Text color="helper">
+                Select an Env Group to load into your application.
+              </Text>
+              <Spacer y={0.5} />
+              <GroupModalSections>
+                <SidebarSection $expanded={!selectedEnvGroup}>
+                  <EnvGroupList>{renderEnvGroupList()}</EnvGroupList>
+                </SidebarSection>
+                {selectedEnvGroup && (
+                  <>
+                    <SidebarSection>
+                      <GroupEnvPreview>
+                        {isObject(selectedEnvGroup?.variables) ||
+                        isObject(selectedEnvGroup?.secret_variables) ? (
+                          <>
+                            {[
+                              ...Object.entries(
+                                selectedEnvGroup?.variables || {}
+                              ).map(([key, value]) => ({
+                                source: "variables",
+                                key,
+                                value,
+                              })),
+                              ...Object.entries(
+                                selectedEnvGroup?.secret_variables || {}
+                              ).map(([key, value]) => ({
+                                source: "secret_variables",
+                                key,
+                                value,
+                              })),
+                            ].map(({ key, value, source }, index) => (
                               <div key={index}>
                                 <span className="key">{key} = </span>
-                                <span className="value">{formattedEnvironmentValue(source === 'secret_variables' ? "****" : value)}</span>
+                                <span className="value">
+                                  {formattedEnvironmentValue(
+                                    source === "secret_variables"
+                                      ? "****"
+                                      : value
+                                  )}
+                                </span>
                               </div>
                             ))}
-                        </>
-                      ) : (
-                        <>This environment group has no variables</>
-                      )
-                    }
-                  </GroupEnvPreview>
-                </SidebarSection>
-
-                </>
-              )
-
-              }
-
-            </GroupModalSections>
-            <Spacer y={1} />
-
-            <Spacer y={1} />
-          </>
+                          </>
+                        ) : (
+                          <>This environment group has no variables</>
+                        )}
+                      </GroupEnvPreview>
+                    </SidebarSection>
+                  </>
+                )}
+              </GroupModalSections>
+              <Spacer y={1} />
+
+              <Spacer y={1} />
+            </>
+          ) : loading ? (
+            <LoadingWrapper>
+              <Loading />
+            </LoadingWrapper>
           ) : (
-
-            loading ? (
-              < LoadingWrapper >
-                < Loading />
-              </LoadingWrapper>)
-              : (<Text >
-                No selectable Env Groups
-              </Text>)
-
-          )
-
-          }
+            <Text>No selectable Env Groups</Text>
+          )}
         </ScrollableContainer>
       </ColumnContainer>
       <SubmitButtonContainer>
-
-        <Button
-          onClick={onSubmit}
-          disabled={!selectedEnvGroup}
-        >
+        <Button onClick={onSubmit} disabled={!selectedEnvGroup}>
           Load Env Group
         </Button>
       </SubmitButtonContainer>
-
-
-    </Modal >
-  )
-}
+    </Modal>
+  );
+};
 
 export default withRouter(EnvGroupModal);
 
 const LoadingWrapper = styled.div`
-height: 150px;
+  height: 150px;
 `;
 const Placeholder = styled.div`
-width: 100%;
-height: 150px;
-display: flex;
-align-items: center;
-justify-content: center;
-color: #aaaabb;
-font-size: 13px;
+  width: 100%;
+  height: 150px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #aaaabb;
+  font-size: 13px;
 `;
 
 const EnvGroupRow = styled.div<{ lastItem?: boolean; isSelected: boolean }>`
-display: flex;
-width: 100%;
-font-size: 13px;
-border-bottom: 1px solid
-  ${(props) => (props.lastItem ? "#00000000" : "#606166")};
-color: #ffffff;
-user-select: none;
-align-items: center;
-padding: 10px 0px;
-cursor: pointer;
-background: ${(props) => (props.isSelected ? "#ffffff11" : "")};
-:hover {
-  background: #ffffff11;
-}
+  display: flex;
+  width: 100%;
+  font-size: 13px;
+  border-bottom: 1px solid
+    ${(props) => (props.lastItem ? "#00000000" : "#606166")};
+  color: #ffffff;
+  user-select: none;
+  align-items: center;
+  padding: 10px 0px;
+  cursor: pointer;
+  background: ${(props) => (props.isSelected ? "#ffffff11" : "")};
+  :hover {
+    background: #ffffff11;
+  }
 
-> img,
-i {
-  width: 16px;
-  height: 18px;
-  margin-left: 12px;
-  margin-right: 12px;
-  font-size: 20px;
-}
+  > img,
+  i {
+    width: 16px;
+    height: 18px;
+    margin-left: 12px;
+    margin-right: 12px;
+    font-size: 20px;
+  }
 `;
 const EnvGroupList = styled.div`
-width: 100%;
-border-radius: 3px;
-background: #ffffff11;
-border: 1px solid #ffffff44;
-overflow-y: auto;
+  width: 100%;
+  border-radius: 3px;
+  background: #ffffff11;
+  border: 1px solid #ffffff44;
+  overflow-y: auto;
 `;
 
 const SidebarSection = styled.section<{ $expanded?: boolean }>`
@@ -339,16 +329,16 @@ const GroupModalSections = styled.div`
 const ColumnContainer = styled.div`
   display: flex;
   flex-direction: column;
-  align-items: stretch; 
+  align-items: stretch;
 `;
 
 const ScrollableContainer = styled.div`
-  flex: 1; 
+  flex: 1;
   overflow-y: auto;
-  max-height: 300px; 
+  max-height: 300px;
 `;
 
 const SubmitButtonContainer = styled.div`
   margin-top: 10px;
   text-align: right;
-`;
+`;

+ 75 - 91
dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvGroupModal.tsx

@@ -1,24 +1,26 @@
-import { type PorterAppFormData } from "lib/porter-apps";
 import React, {
-  type Dispatch,
-  type SetStateAction,
   useCallback,
   useEffect,
   useMemo,
   useState,
+  type Dispatch,
+  type SetStateAction,
 } from "react";
 import { UseFieldArrayAppend, useFormContext } from "react-hook-form";
+import styled, { css } from "styled-components";
+import { type IterableElement } from "type-fest";
+
+import Button from "components/porter/Button";
+import Modal from "components/porter/Modal";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import { type PorterAppFormData } from "lib/porter-apps";
 
-import sliders from "assets/sliders.svg";
 import doppler from "assets/doppler.png";
+import sliders from "assets/sliders.svg";
 
+import EnvGroupRow from "./EnvGroupRow";
 import { type PopulatedEnvGroup } from "./types";
-import Text from "components/porter/Text";
-import Spacer from "components/porter/Spacer";
-import Modal from "components/porter/Modal";
-import styled, { css } from "styled-components";
-import Button from "components/porter/Button";
-import { type IterableElement } from "type-fest";
 
 type Props = {
   baseEnvGroups: PopulatedEnvGroup[];
@@ -27,10 +29,8 @@ type Props = {
 };
 
 const EnvGroupModal: React.FC<Props> = ({ append, setOpen, baseEnvGroups }) => {
-  const [
-    selectedEnvGroup,
-    setSelectedEnvGroup,
-  ] = useState<PopulatedEnvGroup | null>(null);
+  const [selectedEnvGroup, setSelectedEnvGroup] =
+    useState<PopulatedEnvGroup | null>(null);
 
   const { watch } = useFormContext<PorterAppFormData>();
   const envGroups = watch("app.envGroups", []);
@@ -52,65 +52,44 @@ const EnvGroupModal: React.FC<Props> = ({ append, setOpen, baseEnvGroups }) => {
   }, [envGroups, baseEnvGroups]);
 
   return (
-    <Modal closeModal={() => { setOpen(false); }}>
+    <Modal
+      closeModal={() => {
+        setOpen(false);
+      }}
+    >
       <Text size={16}>Load env group</Text>
       <Spacer height="15px" />
       {remainingEnvGroupOptions.length ? (
         <>
           <Text color="helper">
-            Select an Env Group to load into your application.
+            Select an env group to sync to your application.
           </Text>
           <Spacer y={1} />
-          <GroupModalSections>
-            <SidebarSection $expanded={!selectedEnvGroup}>
-              <EnvGroupList>
-                {remainingEnvGroupOptions.map((eg, i) => (
-                  <EnvGroupRow
-                    key={eg.name}
+          <ScrollableContainer>
+            <EnvGroupList>
+              {remainingEnvGroupOptions.map((eg, i) => (
+                <EnvRowWrapper key={i}>
+                  <EnvGroupRow envGroup={eg} maxHeight="300px" noLink />
+                  <SelectedIndicator
+                    onClick={() => {
+                      setSelectedEnvGroup(eg);
+                    }}
                     isSelected={
                       Boolean(selectedEnvGroup) &&
                       selectedEnvGroup?.name === eg.name
                     }
-                    lastItem={i === remainingEnvGroupOptions?.length - 1}
-                    onClick={() => {
-                      setSelectedEnvGroup(eg);
-                    }}
                   >
-                    {eg.type === "doppler" ? (
-                      <img src={doppler} />
-                        ) : (
-                    <img src={sliders} />
-                    )}
-                    {eg.name}
-                  </EnvGroupRow>
-                ))}
-              </EnvGroupList>
-            </SidebarSection>
-            {selectedEnvGroup && (
-              <>
-                <SidebarSection>
-                  <GroupEnvPreview>
-                    {Object.entries(selectedEnvGroup?.variables || {}).map(
-                      ([key, value]) => (
-                        <div key={key}>
-                          <span className="key">{key} = </span>
-                          <span className="value">{value}</span>
-                        </div>
-                      )
+                    {Boolean(selectedEnvGroup) &&
+                    selectedEnvGroup?.name === eg.name ? (
+                      <Check className="material-icons">check</Check>
+                    ) : (
+                      <i className="material-icons">add</i>
                     )}
-                    {Object.entries(
-                      selectedEnvGroup?.secret_variables || {}
-                    ).map(([key, value]) => (
-                      <div key={key}>
-                        <span className="key">{key} = </span>
-                        <span className="value">{value}</span>
-                      </div>
-                    ))}
-                  </GroupEnvPreview>
-                </SidebarSection>
-              </>
-            )}
-          </GroupModalSections>
+                  </SelectedIndicator>
+                </EnvRowWrapper>
+              ))}
+            </EnvGroupList>
+          </ScrollableContainer>
         </>
       ) : (
         <Text>No selectable Env Groups</Text>
@@ -125,38 +104,51 @@ const EnvGroupModal: React.FC<Props> = ({ append, setOpen, baseEnvGroups }) => {
 
 export default EnvGroupModal;
 
-const EnvGroupRow = styled.div<{ lastItem?: boolean; isSelected: boolean }>`
-  display: flex;
-  width: 100%;
-  font-size: 13px;
-  border-bottom: 1px solid
-    ${(props) => (props.lastItem ? "#00000000" : "#606166")};
+const Check = styled.i`
   color: #ffffff;
-  user-select: none;
+  background: #ffffff33;
+  width: 24px;
+  height: 23px;
+  z-index: 0;
+  display: flex;
   align-items: center;
-  padding: 10px 0px;
+  justify-content: center;
+  border-radius: 50%;
+`;
+
+const SelectedIndicator = styled.div<{ isSelected: boolean }>`
+  position: absolute;
+  top: 17px;
+  right: 20px;
+  width: 25px;
+  height: 25px;
+  border: 1px solid ${(props) => (props.isSelected ? "#ffffff" : "#ffffff55")};
+  border-radius: 50%;
   cursor: pointer;
-  background: ${(props) => (props.isSelected ? "#ffffff11" : "")};
+  display: flex;
+  z-index: 1;
+  align-items: center;
+  justify-content: center;
   :hover {
+    border-color: #ffffff;
     background: #ffffff11;
   }
 
-  > img,
-  i {
-    width: 16px;
-    height: 18px;
-    margin-left: 12px;
-    margin-right: 12px;
-    font-size: 20px;
+  > i {
+    font-size: 18px;
+    color: #ffffff;
   }
 `;
+
+const EnvRowWrapper = styled.div`
+  transition: all 0.1s;
+  position: relative;
+`;
+
 const EnvGroupList = styled.div`
-  width: 100%;
-  border-radius: 3px;
-  background: #ffffff11;
-  border: 1px solid #ffffff44;
-  overflow-y: auto;
-  max-height: 340px;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
 `;
 
 const SidebarSection = styled.section<{ $expanded?: boolean }>`
@@ -182,17 +174,9 @@ const GroupEnvPreview = styled.pre`
     color: #3a48ca;
   }
 `;
-const GroupModalSections = styled.div`
-  width: 100%;
-  height: 100%;
-  display: grid;
-  gap: 10px;
-  grid-template-columns: 1fr 1fr;
-  max-height: 365px;
-`;
 
 const ScrollableContainer = styled.div`
   flex: 1;
   overflow-y: auto;
-  max-height: 300px;
+  max-height: 480px;
 `;

+ 35 - 25
dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvGroupRow.tsx

@@ -16,7 +16,8 @@ import doppler from "assets/doppler.png";
 import key from "assets/key.svg";
 
 type Props = {
-  onRemove: (name: string) => void;
+  maxHeight?: string;
+  onRemove?: (name: string) => void;
   envGroup: {
     name: string;
     type: string;
@@ -24,13 +25,16 @@ type Props = {
     secret_variables: Record<string, string>;
   };
   canDelete?: boolean;
+  noLink?: boolean;
 };
 
 // TODO: support footer for consolidation w/ app services
 const EnvGroupRow: React.FC<Props> = ({
+  maxHeight,
   envGroup,
   onRemove,
   canDelete = true,
+  noLink = false,
 }) => {
   const { currentProject } = useContext(Context);
   const history = useHistory();
@@ -61,6 +65,7 @@ const EnvGroupRow: React.FC<Props> = ({
 
   return (
     <Expandable
+      maxHeight={maxHeight}
       header={
         <Container row spaced>
           <Container row>
@@ -78,30 +83,35 @@ const EnvGroupRow: React.FC<Props> = ({
             <Text size={14}>{envGroup.name}</Text>
           </Container>
           <Container row>
-            <Svg
-              onClick={() => {
-                history.push(
-                  envGroupPath(currentProject, `/${envGroup.name}/synced-apps`)
-                );
-              }}
-              data-testid="geist-icon"
-              fill="none"
-              height="27px"
-              shape-rendering="geometricPrecision"
-              stroke="currentColor"
-              stroke-linecap="round"
-              strokeLinejoin="round"
-              stroke-width="2"
-              viewBox="0 0 24 24"
-              width="27px"
-              data-darkreader-inline-stroke=""
-              data-darkreader-inline-color=""
-            >
-              <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path>
-              <path d="M15 3h6v6"></path>
-              <path d="M10 14L21 3"></path>
-            </Svg>
-            {canDelete && (
+            {!noLink && (
+              <Svg
+                onClick={() => {
+                  history.push(
+                    envGroupPath(
+                      currentProject,
+                      `/${envGroup.name}/synced-apps`
+                    )
+                  );
+                }}
+                data-testid="geist-icon"
+                fill="none"
+                height="27px"
+                shape-rendering="geometricPrecision"
+                stroke="currentColor"
+                stroke-linecap="round"
+                strokeLinejoin="round"
+                stroke-width="2"
+                viewBox="0 0 24 24"
+                width="27px"
+                data-darkreader-inline-stroke=""
+                data-darkreader-inline-color=""
+              >
+                <path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path>
+                <path d="M15 3h6v6"></path>
+                <path d="M10 14L21 3"></path>
+              </Svg>
+            )}
+            {canDelete && onRemove && (
               <>
                 <Spacer inline x={0.5} />
                 <I