Ver código fonte

Aurora FE (#3850)

Co-authored-by: Justin Rhee <jusrhee@Justins-MacBook-Air-2.local>
jusrhee 2 anos atrás
pai
commit
26f1226931

+ 146 - 136
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -958,10 +958,10 @@ const EnvGroupVariablesEditor = ({
 };
 
 const EnvGroupSettings = ({
-                            envGroup,
-                            handleDeleteEnvGroup,
-                            namespace,
-                          }: {
+  envGroup,
+  handleDeleteEnvGroup,
+  namespace,
+} : {
   envGroup: EditableEnvGroup;
   handleDeleteEnvGroup: () => void;
   namespace?: string;
@@ -973,8 +973,13 @@ const EnvGroupSettings = ({
     setCurrentError,
   } = useContext(Context);
   const [isAuthorized] = useAuth();
-  const [name, setName] = useState(null);
-  const [cloneNamespace, setCloneNamespace] = useState(null);
+
+  // When cloning an env group, append "-2" for the default name
+  // (i.e. my-env-group-2)
+  const [name, setName] = useState<string>(
+    envGroup.name + "-2"
+  );
+  const [cloneNamespace, setCloneNamespace] = useState<string>("default");
   const [cloneSuccess, setCloneSuccess] = useState(false);
 
   const canDelete = useMemo(() => {
@@ -1013,72 +1018,75 @@ const EnvGroupSettings = ({
       );
       setCloneSuccess(true);
     } catch (error) {
-      setCurrentError(error);
+      console.log(error);
     }
   };
 
   return (
-      <TabWrapper>
-        {isAuthorized("env_group", "", ["get", "delete"]) && (
-            <InnerWrapper full={true}>
-              <Heading isAtTop>Manage environment group</Heading>
+    <TabWrapper>
+      {isAuthorized("env_group", "", ["get", "delete"]) && (
+        <InnerWrapper full={true}>
+          <Heading isAtTop>Manage environment group</Heading>
+          <Helper>
+            Permanently delete this set of environment variables. This action
+            cannot be undone.
+          </Helper>
+          {!canDelete && (
+            <Helper color="#f5cb42">
+              Applications are still synced to this env group. Navigate to
+              "Linked applications" and remove this env group from all
+              applications to delete.
+            </Helper>
+          )}
+          <Button
+            color="#b91133"
+            onClick={() => {
+              setCurrentOverlay({
+                message: `Are you sure you want to delete ${envGroup.name}?`,
+                onYes: handleDeleteEnvGroup,
+                onNo: () => setCurrentOverlay(null),
+              });
+            }}
+            disabled={!canDelete}
+          >
+            Delete {envGroup.name}
+          </Button>
+          {!currentProject?.simplified_view_enabled && (
+            <>
+              <DarkMatter />
+              <Heading>Clone environment group</Heading>
               <Helper>
-                Permanently delete this set of environment variables. This action
-                cannot be undone.
+                Clone this set of environment variables into a new env group.
               </Helper>
-              {!canDelete && (
-                  <Helper color="#f5cb42">
-                    Applications are still synced to this env group. Navigate to
-                    "Linked applications" and remove this env group from all
-                    applications to delete.
-                  </Helper>
-              )}
-              <Button
-                  color="#b91133"
-                  onClick={() => {
-                    setCurrentOverlay({
-                      message: `Are you sure you want to delete ${envGroup.name}?`,
-                      onYes: handleDeleteEnvGroup,
-                      onNo: () => setCurrentOverlay(null),
-                    });
-                  }}
-                  disabled={!canDelete}
-              >
-                Delete {envGroup.name}
-              </Button>
-              <DarkMatter />
-              {!currentProject?.simplified_view_enabled && (<>
-                <Heading>Clone environment group</Heading>
-                <Helper>
-                  Clone this set of environment variables into a new env group.
-                </Helper>
-                <InputRow
-                    type="string"
-                    value={name}
-                    setValue={(x: string) => setName(x)}
-                    label="New env group name"
-                    placeholder="ex: my-cloned-env-group"
-                />
+              <InputRow
+                type="string"
+                value={name}
+                setValue={(x: string) => setName(x)}
+                label="New env group name"
+                placeholder="ex: my-cloned-env-group"
+              />
                 <InputRow
-                    type="string"
-                    value={cloneNamespace}
-                    setValue={(x: string) => setCloneNamespace(x)}
-                    label="New env group namespace"
-                    placeholder="ex: default"
+                  type="string"
+                  value={cloneNamespace}
+                  setValue={(x: string) => setCloneNamespace(x)}
+                  label="New env group namespace"
+                  placeholder="ex: default"
                 />
-                <FlexAlt>
-                  <Button onClick={cloneEnvGroup}>Clone {envGroup.name}</Button>
-                  {cloneSuccess && (
-                      <StatusWrapper position="right" successful={true}>
-                        <i className="material-icons">done</i>
-                        <StatusTextWrapper>Successfully cloned</StatusTextWrapper>
-                      </StatusWrapper>
-                  )}
-                </FlexAlt>
-              </>)}
-            </InnerWrapper>
-        )}
-      </TabWrapper>
+              )}
+              <FlexAlt>
+                <Button onClick={cloneEnvGroup}>Clone {envGroup.name}</Button>
+                {cloneSuccess && (
+                  <StatusWrapper position="right" successful={true}>
+                    <i className="material-icons">done</i>
+                    <StatusTextWrapper>Successfully cloned</StatusTextWrapper>
+                  </StatusWrapper>
+                )}
+              </FlexAlt>
+            </>
+          )}
+        </InnerWrapper>
+      )}
+    </TabWrapper>
   );
 };
 
@@ -1086,78 +1094,80 @@ const ApplicationsList = ({ envGroup }: { envGroup: EditableEnvGroup }) => {
   const { currentCluster, currentProject } = useContext(Context);
 
   return (
-      <>
-        <HeadingWrapper>
-          <Heading isAtTop>Linked applications:</Heading>
-          <DocsHelper
-              link="https://docs.porter.run/deploying-applications/environment-groups#syncing-environment-groups-to-applications"
-              tooltipText="When env group sync is enabled, the applications are automatically restarted when the env groups are updated."
-              placement="top-start"
-              disableMargin
-          />
-        </HeadingWrapper>
-        {currentProject?.simplified_view_enabled ? (
-                envGroup.linked_applications.map((appName) => {
-                  return (
-                      <StyledCard>
-                        <Flex>
-                          <ContentContainer>
-                            <EventInformation>
-                              <EventName>{appName}</EventName>
-                            </EventInformation>
-                          </ContentContainer>
-                          <ActionContainer>
-                            {currentProject?.simplified_view_enabled ? (<ActionButton
-                                    to={`/apps/${appName}`}
-                                    target="_blank"
-                                >
-                                  <span className="material-icons-outlined">open_in_new</span>
-                                </ActionButton>)
-                                :
-                                (<ActionButton
-                                    to={`/applications/${currentCluster.name}/${envGroup.namespace}/${appName}`}
-                                    target="_blank"
-                                >
-                                  <span className="material-icons-outlined">open_in_new</span>
-                                </ActionButton>)}
-                          </ActionContainer>
-                        </Flex>
-                      </StyledCard>
-                  );
-                })
-            )
-            :
-            (envGroup.applications.map((appName) => {
-              return (
-                  <StyledCard>
-                    <Flex>
-                      <ContentContainer>
-                        <EventInformation>
-                          <EventName>{appName}</EventName>
-                        </EventInformation>
-                      </ContentContainer>
-                      <ActionContainer>
-                        {currentProject?.simplified_view_enabled ? (<ActionButton
-                                to={`/apps/${appName}`}
-                                target="_blank"
-                            >
-                              <span className="material-icons-outlined">open_in_new</span>
-                            </ActionButton>)
-                            :
-                            (<ActionButton
-                                to={`/applications/${currentCluster.name}/${envGroup.namespace}/${appName}`}
-                                target="_blank"
-                            >
-                              <span className="material-icons-outlined">open_in_new</span>
-                            </ActionButton>)}
-                      </ActionContainer>
-                    </Flex>
-                  </StyledCard>
-              );
-            }))
-        }
-
-      </>
+    <>
+      <HeadingWrapper>
+        <Heading isAtTop>Linked applications:</Heading>
+        <DocsHelper
+          link="https://docs.porter.run/deploying-applications/environment-groups#syncing-environment-groups-to-applications"
+          tooltipText="When env group sync is enabled, the applications are automatically restarted when the env groups are updated."
+          placement="top-start"
+          disableMargin
+        />
+      </HeadingWrapper>
+      {currentProject?.simplified_view_enabled ? (
+        envGroup.linked_applications.map((appName) => {
+          return (
+            <StyledCard>
+              <Flex>
+                <ContentContainer>
+                  <EventInformation>
+                    <EventName>{appName}</EventName>
+                  </EventInformation>
+                </ContentContainer>
+                <ActionContainer>
+                  {currentProject?.simplified_view_enabled ? (
+                    <ActionButton
+                      to={`/apps/${appName}`}
+                      target="_blank"
+                    >
+                      <span className="material-icons-outlined">open_in_new</span>
+                    </ActionButton>
+                    ) : (
+                    <ActionButton
+                      to={`/applications/${currentCluster.name}/${envGroup.namespace}/${appName}`}
+                      target="_blank"
+                    >
+                      <span className="material-icons-outlined">open_in_new</span>
+                    </ActionButton>
+                  )}
+                </ActionContainer>
+              </Flex>
+            </StyledCard>
+          );
+        })
+      ) : (
+        envGroup.applications.map((appName) => {
+          return (
+            <StyledCard>
+              <Flex>
+                <ContentContainer>
+                  <EventInformation>
+                    <EventName>{appName}</EventName>
+                  </EventInformation>
+                </ContentContainer>
+                <ActionContainer>
+                  {currentProject?.simplified_view_enabled ? (
+                    <ActionButton
+                      to={`/apps/${appName}`}
+                      target="_blank"
+                    >
+                      <span className="material-icons-outlined">open_in_new</span>
+                    </ActionButton>
+                  ) : (
+                    <ActionButton
+                      to={`/applications/${currentCluster.name}/${envGroup.namespace}/${appName}`}
+                      target="_blank"
+                    >
+                      <span className="material-icons-outlined">open_in_new</span>
+                    </ActionButton>
+                  )}
+                </ActionContainer>
+              </Flex>
+            </StyledCard>
+          );
+        })
+      )}
+    </>
   );
 };
 

+ 27 - 8
dashboard/src/main/home/database-dashboard/CreateDatabase.tsx

@@ -12,6 +12,7 @@ import awsElastiCache from "assets/aws-elasticache.png";
 import { Context } from "shared/Context";
 import api from "shared/api";
 import { search } from "shared/search";
+import { AddonCard } from "shared/types";
 
 import TemplateList from "../launch/TemplateList";
 import SearchBar from "components/porter/SearchBar";
@@ -21,7 +22,8 @@ import Back from "components/porter/Back";
 import Fieldset from "components/porter/Fieldset";
 import Text from "components/porter/Text";
 import Container from "components/porter/Container";
-import RDSForm from "./RDSForm";
+import RDSForm from "./forms/RDSForm";
+import AuroraPostgresForm from "./forms/AuroraPostgresForm";
 
 type Props = {
 };
@@ -32,13 +34,19 @@ const CreateDatabase: React.FC<Props> = ({
   const [isLoading, setIsLoading] = useState<boolean>(true);
   const [searchValue, setSearchValue] = useState("");
   const [currentTemplate, setCurrentTemplate] = useState<any>(null);
-  const [databaseTemplates, setDatabaseTemplates] = useState<any[]>([
+  const [databaseTemplates, setDatabaseTemplates] = useState<AddonCard[]>([
     {
       id: "rds-postgresql",
       icon: awsRDS,
-      name: "RDS Postgres",
+      name: "RDS PostgreSQL",
       description: "Amazon Relational Database Service (RDS) is a web service that makes it easier to set up, operate, and scale a relational database in the cloud.",
     },
+    {
+      id: "rds-postgresql-aurora",
+      icon: awsRDS,
+      name: "Aurora PostgreSQL",
+      description: "Amazon Aurora PostgreSQL is a fully managed, PostgreSQL–compatible, and ACID–compliant relational database engine that combines the speed, reliability, and manageability of Amazon Aurora with the simplicity and cost-effectiveness of open-source databases.",
+    },
     {
       id: "elasticache-redis",
       icon: awsElastiCache,
@@ -71,11 +79,22 @@ const CreateDatabase: React.FC<Props> = ({
     <StyledTemplateComponent>
       {
         (currentTemplate) ? (
-          <RDSForm
-            currentTemplate={currentTemplate}
-            goBack={() => setCurrentTemplate(null)}
-            repoURL={capabilities?.default_addon_helm_repo_url}
-          />
+          <>
+            {currentTemplate.id === "rds-postgresql" && (
+              <RDSForm
+                currentTemplate={currentTemplate}
+                goBack={() => setCurrentTemplate(null)}
+                repoURL={capabilities?.default_addon_helm_repo_url}
+              />
+            )}
+            {currentTemplate.id === "rds-postgresql-aurora" && (
+              <AuroraPostgresForm
+                currentTemplate={currentTemplate}
+                goBack={() => setCurrentTemplate(null)}
+                repoURL={capabilities?.default_addon_helm_repo_url}
+              />
+            )}
+          </>
         ) : (
           <>
             <Back to="/databases" />

+ 1 - 0
dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx

@@ -42,6 +42,7 @@ type Props = {};
 
 const templateWhitelist = [
   "rds-postgresql",
+  "rds-postgresql-aurora",
 ];
 
 const Apps: React.FC<Props> = ({ 

+ 377 - 0
dashboard/src/main/home/database-dashboard/forms/AuroraPostgresForm.tsx

@@ -0,0 +1,377 @@
+import React, { useEffect, useState, useContext } from "react";
+import styled from "styled-components";
+import _ from "lodash";
+import { v4 as uuidv4 } from 'uuid';
+
+import { hardcodedIcons } from "shared/hardcodedNameDict";
+import { Context } from "shared/Context";
+import api from "shared/api";
+import { pushFiltered } from "shared/routing";
+
+import Back from "components/porter/Back";
+import DashboardHeader from "../../cluster-dashboard/DashboardHeader";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import Input from "components/porter/Input";
+import VerticalSteps from "components/porter/VerticalSteps";
+import Button from "components/porter/Button";
+import { RouteComponentProps, withRouter } from "react-router";
+import Error from "components/porter/Error";
+import Fieldset from "components/porter/Fieldset";
+import Container from "components/porter/Container";
+import ClickToCopy from "components/porter/ClickToCopy";
+
+type Props = RouteComponentProps & {
+  currentTemplate: any;
+  goBack: () => void;
+  repoURL: string | undefined;
+};
+
+const AuroraPostgresForm: React.FC<Props> = ({
+  currentTemplate,
+  goBack,
+  repoURL,
+  ...props
+}) => {
+  const { currentCluster, currentProject } = useContext(Context);
+  const [currentStep, setCurrentStep] = useState<number>(0);
+  const [name, setName] = useState<string>("");
+  const [buttonStatus, setButtonStatus] = useState<string>("");
+  const [credentialsSaved, setCredentialsSaved] = useState<boolean>(false);
+  const [dbName, setDbName] = useState<string>("postgres");
+  const [dbPassword, setDbPassword] = useState<string>(uuidv4());
+  const [dbUsername, setDbUsername] = useState<string>("postgres");
+  const [storage, setStorage] = useState<number>(0);
+  const [tier, setTier] = useState<string>("");
+  const [hidePassword, setHidePassword] = useState<boolean>(true);
+
+  useEffect(() => {
+    if (currentStep === 1) {
+      setCurrentStep(3);
+    }
+  }, [tier]);
+
+  const waitForHelmRelease = () => {
+    setTimeout(() => {
+      api.getChart(
+        "<token>",
+        {},
+        {
+          id: currentProject?.id || -1,
+          namespace: "ack-system",
+          cluster_id: currentCluster?.id || -1,
+          name,
+          revision: 0,
+        }
+      )
+        .then((res) => {
+          if (res?.data?.version) {
+            setButtonStatus("success");
+            pushFiltered(props, "/databases", ["project_id"], {
+              cluster: currentCluster?.name,
+            });
+          } else {
+            waitForHelmRelease();
+          }
+        })
+        .catch((err) => {
+          waitForHelmRelease();
+        });
+    }, 500);
+  };
+
+  const deploy = async () => {
+    setButtonStatus("loading");
+
+    const values = {
+      config: {
+        name,
+        masterUserPassword: dbPassword,
+        allocatedStorage: storage,
+        instanceClass: tier,
+      }
+    }
+
+    api
+      .deployAddon(
+        "<token>",
+        {
+          template_name: "rds-postgresql-aurora",
+          template_version: "latest",
+          values,
+          name,
+        },
+        {
+          id: currentProject?.id || -1,
+          cluster_id: currentCluster?.id || -1,
+          namespace: "ack-system",
+          repo_url: repoURL,
+        }
+      )
+      .then((_) => {
+        waitForHelmRelease();
+      })
+      .catch((err) => {
+        let parsedErr = err?.response?.data?.error;
+        err = parsedErr || err.message || JSON.stringify(err);
+        setButtonStatus(err);
+        return;
+      });
+  };
+
+  const getStatus = () => {
+    if (!buttonStatus) {
+      return;
+    }
+    if (buttonStatus === "loading" || buttonStatus === "success") {
+      return buttonStatus;
+    } else {
+      return (
+        <Error message={buttonStatus} />
+      );
+    }
+  };
+
+  return (
+    <CenterWrapper>
+      <Div>
+        <StyledConfigureTemplate>
+          <Back onClick={goBack} />
+          <DashboardHeader
+            prefix={
+              <Icon 
+                src={hardcodedIcons[currentTemplate.name] || currentTemplate.icon}
+              />
+            }
+            title="Create an Aurora PostgreSQL instance"
+            capitalize={false}
+            disableLineBreak
+          />
+          <DarkMatter />
+          <VerticalSteps
+            currentStep={currentStep}
+            steps={[
+              <>
+                <Text size={16}>Database name</Text>
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  Lowercase letters, numbers, and "-" only.
+                </Text>
+                <Spacer height="20px" />
+                <Input
+                  placeholder="ex: my-database"
+                  value={name}
+                  width="300px"
+                  setValue={(e) => {
+                    if (e) {
+                      credentialsSaved ? setCurrentStep(2) : setCurrentStep(1);
+                    } else {
+                      setCurrentStep(0);
+                    }
+                    setName(e);
+                  }}
+                />
+              </>,
+              <>
+                <Text size={16}>Database resources</Text>
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  Specify your database CPU, RAM, and storage.
+                </Text>
+                <Spacer y={.5} />
+                <Text>
+                  Select an instance tier:
+                </Text>
+                <Spacer height="20px" />
+                <ResourceOption
+                  selected={tier === "db.t4g.medium"}
+                  onClick={() => {
+                    setStorage(100);
+                    setTier("db.t4g.medium");
+                  }}
+                >
+                  <Container row>
+                    <Text>Medium</Text>
+                    <Spacer inline width="5px" />
+                    <Text color="helper">- 2 CPU, 4 GB RAM</Text>
+                  </Container>
+                  <StorageTag>100 GB Storage</StorageTag>
+                </ResourceOption>
+                <Spacer height="15px" />
+                <ResourceOption
+                  selected={tier === "db.t4g.large"}
+                  onClick={() => {
+                    setStorage(256);
+                    setTier("db.t4g.large");
+                  }}
+                >
+                  <Container row>
+                    <Text>Large</Text>
+                    <Spacer inline width="5px" />
+                    <Text color="helper">- 2 CPU, 8 GB RAM</Text>
+                  </Container>
+                  <StorageTag>256 GB Storage</StorageTag>
+                </ResourceOption>
+              </>,
+              <>
+                <Text size={16}>Database credentials</Text>
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  These credentials never leave your own cloud environment. You will be able to automatically import these credentials from any app.
+                </Text>
+                <Spacer height="20px" />
+                <Fieldset>
+                  <Text>Postgres DB name</Text>
+                  <Spacer y={0.5} />
+                  <Text
+                    additionalStyles="font-family: monospace;"
+                    color="helper"
+                  >
+                    {dbName}
+                  </Text>
+                  <Spacer y={1} />
+                  <Text>Postgres username</Text>
+                  <Spacer y={0.5} />
+                  <Text
+                    additionalStyles="font-family: monospace;"
+                    color="helper"
+                  >
+                    {dbUsername}
+                  </Text>
+                  <Spacer y={1} />
+                  <Text>Postgres password</Text>
+                  <Spacer y={0.5} />
+                  <Container row>
+                    {hidePassword ? (
+                      <>
+                        <Blur>{dbPassword}</Blur>
+                        <Spacer inline width="10px" />
+                        <RevealButton
+                          onClick={() => setHidePassword(false)}
+                        >
+                          Reveal
+                        </RevealButton>
+                      </>
+                    ) : (
+                      <>
+                        <ClickToCopy color="helper">
+                          {dbPassword}
+                        </ClickToCopy>
+                        <Spacer inline width="10px" />
+                        <RevealButton
+                          onClick={() => setHidePassword(true)}
+                        >
+                          Hide
+                        </RevealButton>
+                      </>
+                    )}
+                  </Container>
+                </Fieldset>
+              </>,
+              <>
+                <Text size={16}>Provision a database</Text>
+                <Spacer y={0.5} />
+                <Button
+                  onClick={deploy}
+                  disabled={buttonStatus === "loading"}
+                  status={getStatus()}
+                >
+                  Create database
+                </Button>
+              </>
+            ]}
+          />
+          <Spacer height="80px" />
+        </StyledConfigureTemplate>
+      </Div>
+    </CenterWrapper>
+  );
+};
+
+export default withRouter(AuroraPostgresForm);
+
+const RevealButton = styled.div`
+  background: ${props => props.theme.fg};
+  padding: 5px 10px;
+  border-radius: 5px;
+  border: 1px solid #494b4f;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  :hover {
+    filter: brightness(120%);
+  }
+`;
+
+const Blur = styled.div`
+  filter: blur(5px);
+  -webkit-filter: blur(5px);
+  position: relative;
+  margin-left: -5px;
+  font-family: monospace;
+`;
+
+const StorageTag = styled.div`
+  background: #202227;
+  color: #aaaabb;
+  border-radius: 5px;
+  padding: 5px 10px;
+  font-size: 13px;
+  margin-left: 5px;
+`;
+
+const ResourceOption = styled.div<{ selected?: boolean }>`
+  background: ${(props) => props.theme.clickable.bg};
+  border: 1px solid ${props => props.selected ? "#ffffff" : props.theme.border};
+  width: 350px;
+  padding: 10px 15px;
+  border-radius: 5px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  cursor: pointer;
+  :hover {
+    border: 1px solid #ffffff;
+  }
+`;
+
+const Div = styled.div`
+  width: 100%;
+  max-width: 900px;
+`;
+
+const CenterWrapper = styled.div`
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+`;
+
+const DarkMatter = styled.div`
+  width: 100%;
+  margin-top: -5px;
+`;
+
+const Icon = styled.img`
+  margin-right: 15px;
+  height: 30px;
+  animation: floatIn 0.5s;
+  animation-fill-mode: forwards;
+
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(20px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
+`;
+
+const StyledConfigureTemplate = styled.div`
+  height: 100%;
+`;

+ 2 - 2
dashboard/src/main/home/database-dashboard/RDSForm.tsx → dashboard/src/main/home/database-dashboard/forms/RDSForm.tsx

@@ -9,7 +9,7 @@ import api from "shared/api";
 import { pushFiltered } from "shared/routing";
 
 import Back from "components/porter/Back";
-import DashboardHeader from "../cluster-dashboard/DashboardHeader";
+import DashboardHeader from "../../cluster-dashboard/DashboardHeader";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import Input from "components/porter/Input";
@@ -154,7 +154,7 @@ const RDSForm: React.FC<Props> = ({
                 src={hardcodedIcons[currentTemplate.name] || currentTemplate.icon}
               />
             }
-            title="Create an RDS Postgres instance"
+            title="Create an RDS PostgreSQL instance"
             capitalize={false}
             disableLineBreak
           />

+ 7 - 0
dashboard/src/shared/types.tsx

@@ -17,6 +17,13 @@ export interface ClusterType {
   cloud_provider: string;
 }
 
+export interface AddonCard {
+  id: string;
+  icon: string;
+  name: string;
+  description: string;
+}
+
 export interface DetailedClusterType extends ClusterType {
   ingress_ip?: string;
   ingress_error?: DetailedIngressError;