Parcourir la source

[POR-2208] Datastore provisioning status bar (#4174)

Feroze Mohideen il y a 2 ans
Parent
commit
cceb3fbd3c

+ 5 - 7
api/server/handlers/datastore/get.go

@@ -95,10 +95,11 @@ func (c *GetDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 			AccountID: awsArn.AccountID,
 			AccountID: awsArn.AccountID,
 			Type:      porterv1.EnumCloudProvider_ENUM_CLOUD_PROVIDER_AWS,
 			Type:      porterv1.EnumCloudProvider_ENUM_CLOUD_PROVIDER_AWS,
 		},
 		},
-		Name:            datastoreName,
-		IncludeEnvGroup: true,
-		IncludeMetadata: true,
-		CCPClient:       c.Config().ClusterControlPlaneClient,
+		Name:                datastoreName,
+		IncludeEnvGroup:     true,
+		IncludeMetadata:     true,
+		CCPClient:           c.Config().ClusterControlPlaneClient,
+		DatastoreRepository: c.Repo().Datastore(),
 	})
 	})
 	if err != nil {
 	if err != nil {
 		err = telemetry.Error(ctx, span, err, "error getting datastore")
 		err = telemetry.Error(ctx, span, err, "error getting datastore")
@@ -117,9 +118,6 @@ func (c *GetDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 		return
 	}
 	}
 	datastore := datastores[0]
 	datastore := datastores[0]
-	datastore.Type = datastoreRecord.Type
-	datastore.Engine = datastoreRecord.Engine
-	datastore.CreatedAtUTC = datastoreRecord.CreatedAt
 
 
 	resp.Datastore = datastore
 	resp.Datastore = datastore
 
 

+ 20 - 5
api/server/handlers/datastore/list.go

@@ -16,6 +16,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
 	"github.com/porter-dev/porter/internal/telemetry"
 	"github.com/porter-dev/porter/internal/telemetry"
 )
 )
 
 
@@ -59,7 +60,7 @@ type Datastore struct {
 	Metadata []*porterv1.DatastoreMetadata `json:"metadata,omitempty"`
 	Metadata []*porterv1.DatastoreMetadata `json:"metadata,omitempty"`
 
 
 	// Status is the status of the datastore
 	// Status is the status of the datastore
-	Status string `json:"status,omitempty"`
+	Status string `json:"status"`
 
 
 	// CreatedAtUTC is the time the datastore was created in UTC
 	// CreatedAtUTC is the time the datastore was created in UTC
 	CreatedAtUTC time.Time `json:"created_at"`
 	CreatedAtUTC time.Time `json:"created_at"`
@@ -106,6 +107,7 @@ func (h *ListDatastoresHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			Type:         datastore.Type,
 			Type:         datastore.Type,
 			Engine:       datastore.Engine,
 			Engine:       datastore.Engine,
 			CreatedAtUTC: datastore.CreatedAt,
 			CreatedAtUTC: datastore.CreatedAt,
+			Status:       string(datastore.Status),
 		})
 		})
 	}
 	}
 
 
@@ -123,7 +125,8 @@ type DatastoresInput struct {
 	IncludeEnvGroup bool
 	IncludeEnvGroup bool
 	IncludeMetadata bool
 	IncludeMetadata bool
 
 
-	CCPClient porterv1connect.ClusterControlPlaneServiceClient
+	CCPClient           porterv1connect.ClusterControlPlaneServiceClient
+	DatastoreRepository repository.DatastoreRepository
 }
 }
 
 
 // Datastores returns a list of datastores associated with the specified project/cloud-provider
 // Datastores returns a list of datastores associated with the specified project/cloud-provider
@@ -174,12 +177,24 @@ func Datastores(ctx context.Context, inp DatastoresInput) ([]Datastore, error) {
 	}
 	}
 
 
 	for _, datastore := range resp.Msg.Datastores {
 	for _, datastore := range resp.Msg.Datastores {
-		datastores = append(datastores, Datastore{
+		encodedDatastore := Datastore{
 			Name:     datastore.Name,
 			Name:     datastore.Name,
-			Type:     datastore.Type.Enum().String(),
 			Metadata: datastore.Metadata,
 			Metadata: datastore.Metadata,
 			Env:      datastore.Env,
 			Env:      datastore.Env,
-		})
+		}
+
+		datastoreRecord, err := inp.DatastoreRepository.GetByProjectIDAndName(ctx, inp.ProjectID, datastore.Name)
+		if err != nil {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "err-datastore-name", Value: datastore.Name})
+			return datastores, telemetry.Error(ctx, span, err, "datastore record not found")
+		}
+
+		encodedDatastore.CreatedAtUTC = datastoreRecord.CreatedAt
+		encodedDatastore.Type = datastoreRecord.Type
+		encodedDatastore.Engine = datastoreRecord.Engine
+		encodedDatastore.Status = string(datastoreRecord.Status)
+
+		datastores = append(datastores, encodedDatastore)
 	}
 	}
 
 
 	return datastores, nil
 	return datastores, nil

+ 12 - 0
api/server/handlers/datastore/update.go

@@ -101,6 +101,18 @@ func (h *UpdateDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		return
 		return
 	}
 	}
 
 
+	updateReq := connect.NewRequest(&porterv1.UpdateDatastoreRequest{
+		ProjectId:   int64(project.ID),
+		DatastoreId: record.ID.String(),
+	})
+
+	_, err = h.Config().ClusterControlPlaneClient.UpdateDatastore(ctx, updateReq)
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "error calling ccp update datastore")
+		h.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
 	// TODO: create an API-level representation of the db model rather than returning the model directly
 	// TODO: create an API-level representation of the db model rather than returning the model directly
 	h.WriteResult(w, r, record)
 	h.WriteResult(w, r, record)
 }
 }

+ 90 - 0
dashboard/src/components/porter/StatusBar.tsx

@@ -0,0 +1,90 @@
+import React from "react";
+import styled from "styled-components";
+
+import LoadingBar from "./LoadingBar";
+import Spacer from "./Spacer";
+import Text from "./Text";
+
+type StatusBarProps = {
+  icon: string;
+  title: string;
+  titleDescriptor?: string;
+  subtitle: string;
+  percentCompleted: number;
+  failureReason?: string;
+};
+
+const StatusBar: React.FC<StatusBarProps> = ({
+  icon,
+  title,
+  titleDescriptor,
+  subtitle,
+  percentCompleted,
+  failureReason,
+}) => {
+  // Component logic here
+
+  return (
+    <StyledProvisionerStatus>
+      <HeaderSection>
+        <TitleSection>
+          <Flex>
+            <Icon src={icon} />
+            {title}
+          </Flex>
+          {titleDescriptor && <Text color="helper">{titleDescriptor}</Text>}
+        </TitleSection>
+        <Spacer height="18px" />
+        <LoadingBar
+          color={failureReason ? "failed" : undefined}
+          percent={percentCompleted}
+        />
+        <Spacer height="18px" />
+        <Text color="#aaaabb">{subtitle}</Text>
+      </HeaderSection>
+      {failureReason && <DummyLogs>Error: {failureReason}</DummyLogs>}
+    </StyledProvisionerStatus>
+  );
+};
+
+export default StatusBar;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const HeaderSection = styled.div`
+  padding: 15px;
+  padding-bottom: 18px;
+`;
+
+const DummyLogs = styled.div`
+  padding: 15px;
+  width: 100%;
+  display: flex;
+  font-size: 13px;
+  background: #101420;
+  font-family: monospace;
+`;
+
+const Icon = styled.img`
+  height: 16px;
+  margin-right: 10px;
+  margin-bottom: -1px;
+`;
+
+const StyledProvisionerStatus = styled.div`
+  border-radius: 5px;
+  background: #26292e;
+  border: 1px solid #494b4f;
+  font-size: 13px;
+  width: 100%;
+  overflow: hidden;
+`;
+
+const TitleSection = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+`;

+ 0 - 1
dashboard/src/components/porter/StatusDot.tsx

@@ -25,7 +25,6 @@ const StyledStatusDot = styled.div<{ color: string; height: number }>`
   max-width: ${(props) => props.height}px;
   max-width: ${(props) => props.height}px;
   height: ${(props) => props.height}px;
   height: ${(props) => props.height}px;
   border-radius: 50%;
   border-radius: 50%;
-  margin-right: 10px;
   background: ${(props) => props.color};
   background: ${(props) => props.color};
 
 
   box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);
   box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);

+ 39 - 1
dashboard/src/lib/databases/types.ts

@@ -24,10 +24,18 @@ export const datastoreValidator = z.object({
   type: z.string(),
   type: z.string(),
   engine: z.string(),
   engine: z.string(),
   created_at: z.string().default(""),
   created_at: z.string().default(""),
-  status: z.string().default(""),
   metadata: datastoreMetadataValidator.array().default([]),
   metadata: datastoreMetadataValidator.array().default([]),
   env: datastoreEnvValidator.optional(),
   env: datastoreEnvValidator.optional(),
   connection_string: z.string().default(""),
   connection_string: z.string().default(""),
+  status: z.enum([
+    "",
+    "CREATING",
+    "CONFIGURING_LOG_EXPORTS",
+    "MODIFYING",
+    "CONFIGURING_ENHANCED_MONITORING",
+    "BACKING_UP",
+    "AVAILABLE",
+  ]),
 });
 });
 
 
 export type SerializedDatastore = z.infer<typeof datastoreValidator>;
 export type SerializedDatastore = z.infer<typeof datastoreValidator>;
@@ -90,6 +98,35 @@ export type DatabaseType =
 export const DATABASE_TYPE_RDS = "RDS" as const;
 export const DATABASE_TYPE_RDS = "RDS" as const;
 export const DATABASE_TYPE_ELASTICACHE = "ELASTICACHE" as const;
 export const DATABASE_TYPE_ELASTICACHE = "ELASTICACHE" as const;
 
 
+export type DatabaseState = {
+  state: z.infer<typeof datastoreValidator>["status"];
+  displayName: string;
+};
+export const DATABASE_STATE_CREATING = {
+  state: "CREATING" as const,
+  displayName: "Creating",
+};
+export const DATABASE_STATE_CONFIGURING_LOG_EXPORTS = {
+  state: "CONFIGURING_LOG_EXPORTS" as const,
+  displayName: "Configuring log exports",
+};
+export const DATABASE_STATE_MODIFYING = {
+  state: "MODIFYING" as const,
+  displayName: "Modifying",
+};
+export const DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING = {
+  state: "CONFIGURING_ENHANCED_MONITORING" as const,
+  displayName: "Configuring enhanced monitoring",
+};
+export const DATABASE_STATE_BACKING_UP = {
+  state: "BACKING_UP" as const,
+  displayName: "Backing up",
+};
+export const DATABASE_STATE_AVAILABLE = {
+  state: "AVAILABLE" as const,
+  displayName: "Finishing provision",
+};
+
 export type DatabaseTemplate = {
 export type DatabaseTemplate = {
   type: DatabaseType;
   type: DatabaseType;
   engine: DatabaseEngine;
   engine: DatabaseEngine;
@@ -99,6 +136,7 @@ export type DatabaseTemplate = {
   disabled: boolean;
   disabled: boolean;
   instanceTiers: ResourceOption[];
   instanceTiers: ResourceOption[];
   formTitle: string;
   formTitle: string;
+  creationStateProgression: DatabaseState[];
 };
 };
 
 
 const instanceTierValidator = z.enum([
 const instanceTierValidator = z.enum([

+ 2 - 2
dashboard/src/main/home/database-dashboard/CreateDatabase.tsx

@@ -7,7 +7,6 @@ import { z } from "zod";
 
 
 import Back from "components/porter/Back";
 import Back from "components/porter/Back";
 import Spacer from "components/porter/Spacer";
 import Spacer from "components/porter/Spacer";
-import Tag from "components/porter/Tag";
 import Text from "components/porter/Text";
 import Text from "components/porter/Text";
 import {
 import {
   DATABASE_ENGINE_AURORA_POSTGRES,
   DATABASE_ENGINE_AURORA_POSTGRES,
@@ -25,6 +24,7 @@ import { SUPPORTED_DATABASE_TEMPLATES } from "./constants";
 import DatabaseFormAuroraPostgres from "./forms/DatabaseFormAuroraPostgres";
 import DatabaseFormAuroraPostgres from "./forms/DatabaseFormAuroraPostgres";
 import DatabaseFormElasticacheRedis from "./forms/DatabaseFormElasticacheRedis";
 import DatabaseFormElasticacheRedis from "./forms/DatabaseFormElasticacheRedis";
 import DatabaseFormRDSPostgres from "./forms/DatabaseFormRDSPostgres";
 import DatabaseFormRDSPostgres from "./forms/DatabaseFormRDSPostgres";
+import EngineTag from "./tags/EngineTag";
 
 
 type Props = RouteComponentProps;
 type Props = RouteComponentProps;
 const CreateDatabase: React.FC<Props> = ({ history, match: queryMatch }) => {
 const CreateDatabase: React.FC<Props> = ({ history, match: queryMatch }) => {
@@ -98,7 +98,7 @@ const CreateDatabase: React.FC<Props> = ({ history, match: queryMatch }) => {
                       <Spacer inline x={0.5} />
                       <Spacer inline x={0.5} />
                     </TemplateHeader>
                     </TemplateHeader>
                     <Spacer y={0.5} />
                     <Spacer y={0.5} />
-                    <Tag hoverable={false}>{engine.displayName}</Tag>
+                    <EngineTag engine={engine} />
                     <Spacer y={0.5} />
                     <Spacer y={0.5} />
                     <TemplateDescription>{description}</TemplateDescription>
                     <TemplateDescription>{description}</TemplateDescription>
                     <Spacer y={0.5} />
                     <Spacer y={0.5} />

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

@@ -79,6 +79,7 @@ export const DatabaseContextProvider: React.FC<
     },
     },
     {
     {
       enabled: paramsExist,
       enabled: paramsExist,
+      refetchInterval: 5000,
       refetchOnWindowFocus: true,
       refetchOnWindowFocus: true,
     }
     }
   );
   );

+ 17 - 4
dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx

@@ -12,7 +12,7 @@ import Fieldset from "components/porter/Fieldset";
 import PorterLink from "components/porter/Link";
 import PorterLink from "components/porter/Link";
 import SearchBar from "components/porter/SearchBar";
 import SearchBar from "components/porter/SearchBar";
 import Spacer from "components/porter/Spacer";
 import Spacer from "components/porter/Spacer";
-import Tag from "components/porter/Tag";
+import StatusDot from "components/porter/StatusDot";
 import Text from "components/porter/Text";
 import Text from "components/porter/Text";
 import Toggle from "components/porter/Toggle";
 import Toggle from "components/porter/Toggle";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
@@ -26,7 +26,6 @@ import database from "assets/database.svg";
 import grid from "assets/grid.png";
 import grid from "assets/grid.png";
 import list from "assets/list.png";
 import list from "assets/list.png";
 import notFound from "assets/not-found.png";
 import notFound from "assets/not-found.png";
-import healthy from "assets/status-healthy.png";
 import time from "assets/time.png";
 import time from "assets/time.png";
 
 
 import EngineTag from "./tags/EngineTag";
 import EngineTag from "./tags/EngineTag";
@@ -149,7 +148,14 @@ const DatabaseDashboard: React.FC = () => {
                           <Icon src={datastore.template.icon} />
                           <Icon src={datastore.template.icon} />
                           <Text size={14}>{datastore.name}</Text>
                           <Text size={14}>{datastore.name}</Text>
                         </Container>
                         </Container>
-                        <MidIcon src={healthy} height="16px" />
+                        <StatusDot
+                          status={
+                            datastore.status === "AVAILABLE"
+                              ? "available"
+                              : "pending"
+                          }
+                          heightPixels={9}
+                        />
                       </Container>
                       </Container>
                       <Container row>
                       <Container row>
                         <EngineTag engine={datastore.template.engine} />
                         <EngineTag engine={datastore.template.engine} />
@@ -177,7 +183,14 @@ const DatabaseDashboard: React.FC = () => {
                         <MidIcon src={datastore.template.icon} />
                         <MidIcon src={datastore.template.icon} />
                         <Text size={14}>{datastore.name}</Text>
                         <Text size={14}>{datastore.name}</Text>
                       </Container>
                       </Container>
-                      <MidIcon src={healthy} height="16px" />
+                      <StatusDot
+                        status={
+                          datastore.status === "AVAILABLE"
+                            ? "available"
+                            : "pending"
+                        }
+                        heightPixels={9}
+                      />
                     </Container>
                     </Container>
                     <Spacer y={0.5} />
                     <Spacer y={0.5} />
                     <Container row>
                     <Container row>

+ 0 - 18
dashboard/src/main/home/database-dashboard/DatabaseHeader.tsx

@@ -2,7 +2,6 @@ import React from "react";
 import styled from "styled-components";
 import styled from "styled-components";
 import { match } from "ts-pattern";
 import { match } from "ts-pattern";
 
 
-import Banner from "components/porter/Banner";
 import Container from "components/porter/Container";
 import Container from "components/porter/Container";
 import Icon from "components/porter/Icon";
 import Icon from "components/porter/Icon";
 import Spacer from "components/porter/Spacer";
 import Spacer from "components/porter/Spacer";
@@ -54,29 +53,12 @@ const DatabaseHeader: React.FC = () => {
         </div>
         </div>
         <Spacer y={0.5} />
         <Spacer y={0.5} />
       </CreatedAtContainer>
       </CreatedAtContainer>
-      {datastoreField(datastore, "status") !== "available" && (
-        <>
-          <Spacer y={1} />
-          <Banner>
-            <BannerContents>
-              <b>Database is being created</b>
-            </BannerContents>
-            <Spacer inline width="5px" />
-          </Banner>
-        </>
-      )}
     </>
     </>
   );
   );
 };
 };
 
 
 export default DatabaseHeader;
 export default DatabaseHeader;
 
 
-const BannerContents = styled.div`
-  display: flex;
-  flex-direction: column;
-  row-gap: 0.5rem;
-`;
-
 const CreatedAtContainer = styled.div`
 const CreatedAtContainer = styled.div`
   display: inline-flex;
   display: inline-flex;
   column-gap: 6px;
   column-gap: 6px;

+ 5 - 0
dashboard/src/main/home/database-dashboard/DatabaseTabs.tsx

@@ -6,6 +6,7 @@ import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
 import TabSelector from "components/TabSelector";
 
 
 import { useDatabaseContext } from "./DatabaseContextProvider";
 import { useDatabaseContext } from "./DatabaseContextProvider";
+import DatastoreProvisioningIndicator from "./DatastoreProvisioningIndicator";
 import ConfigurationTab from "./tabs/ConfigurationTab";
 import ConfigurationTab from "./tabs/ConfigurationTab";
 import ConnectedAppsTab from "./tabs/ConnectedAppsTab";
 import ConnectedAppsTab from "./tabs/ConnectedAppsTab";
 import DatabaseEnvTab from "./tabs/DatabaseEnvTab";
 import DatabaseEnvTab from "./tabs/DatabaseEnvTab";
@@ -50,6 +51,10 @@ const DatabaseTabs: React.FC<DbTabProps> = ({ tabParam }) => {
     ];
     ];
   }, []);
   }, []);
 
 
+  if (datastore.status !== "AVAILABLE") {
+    return <DatastoreProvisioningIndicator />;
+  }
+
   return (
   return (
     <>
     <>
       <TabSelector
       <TabSelector

+ 53 - 0
dashboard/src/main/home/database-dashboard/DatastoreProvisioningIndicator.tsx

@@ -0,0 +1,53 @@
+import React, { useMemo } from "react";
+import { match } from "ts-pattern";
+
+import StatusBar from "components/porter/StatusBar";
+import {
+  DATABASE_TYPE_ELASTICACHE,
+  DATABASE_TYPE_RDS,
+} from "lib/databases/types";
+
+import { useDatabaseContext } from "./DatabaseContextProvider";
+
+const DatastoreProvisioningIndicator: React.FC = () => {
+  const { datastore } = useDatabaseContext();
+
+  const { percentCompleted, title, titleDescriptor } = useMemo(() => {
+    const creationSteps = datastore.template.creationStateProgression.map(
+      (s) => s.state
+    );
+    const stepsCompleted = creationSteps.indexOf(datastore.status) + 1;
+    const percentCompleted =
+      stepsCompleted === -1
+        ? 0
+        : (stepsCompleted / creationSteps.length) * 100.0;
+    const title = match(datastore.template)
+      .with({ type: DATABASE_TYPE_RDS }, () => "RDS provisioning status")
+      .with(
+        { type: DATABASE_TYPE_ELASTICACHE },
+        () => "Elasticache provisioning status"
+      )
+      .exhaustive();
+    const stateMatch = datastore.template.creationStateProgression.find(
+      (s) => s.state === datastore.status
+    );
+    const titleDescriptor = stateMatch
+      ? `${stateMatch.displayName}...`
+      : undefined;
+    return { percentCompleted, title, titleDescriptor };
+  }, [datastore]);
+
+  return (
+    <StatusBar
+      icon={datastore.template.icon}
+      title={title}
+      titleDescriptor={titleDescriptor}
+      subtitle={
+        "Setup can take up to 20 minutes. You can close this window and come back later."
+      }
+      percentCompleted={percentCompleted}
+    />
+  );
+};
+
+export default DatastoreProvisioningIndicator;

+ 24 - 0
dashboard/src/main/home/database-dashboard/constants.ts

@@ -3,6 +3,12 @@ import {
   DATABASE_ENGINE_MEMCACHED,
   DATABASE_ENGINE_MEMCACHED,
   DATABASE_ENGINE_POSTGRES,
   DATABASE_ENGINE_POSTGRES,
   DATABASE_ENGINE_REDIS,
   DATABASE_ENGINE_REDIS,
+  DATABASE_STATE_AVAILABLE,
+  DATABASE_STATE_BACKING_UP,
+  DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING,
+  DATABASE_STATE_CONFIGURING_LOG_EXPORTS,
+  DATABASE_STATE_CREATING,
+  DATABASE_STATE_MODIFYING,
   DATABASE_TYPE_ELASTICACHE,
   DATABASE_TYPE_ELASTICACHE,
   DATABASE_TYPE_RDS,
   DATABASE_TYPE_RDS,
   type DatabaseTemplate,
   type DatabaseTemplate,
@@ -44,6 +50,14 @@ export const SUPPORTED_DATABASE_TEMPLATES: DatabaseTemplate[] = [
       },
       },
     ],
     ],
     formTitle: "Create an RDS PostgreSQL instance",
     formTitle: "Create an RDS PostgreSQL instance",
+    creationStateProgression: [
+      DATABASE_STATE_CREATING,
+      DATABASE_STATE_CONFIGURING_LOG_EXPORTS,
+      DATABASE_STATE_MODIFYING,
+      DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING,
+      DATABASE_STATE_BACKING_UP,
+      DATABASE_STATE_AVAILABLE,
+    ],
   }),
   }),
   Object.freeze({
   Object.freeze({
     name: "Amazon Aurora",
     name: "Amazon Aurora",
@@ -70,6 +84,10 @@ export const SUPPORTED_DATABASE_TEMPLATES: DatabaseTemplate[] = [
       },
       },
     ],
     ],
     formTitle: "Create an Aurora PostgreSQL instance",
     formTitle: "Create an Aurora PostgreSQL instance",
+    creationStateProgression: [
+      DATABASE_STATE_CREATING,
+      DATABASE_STATE_AVAILABLE,
+    ],
   }),
   }),
   Object.freeze({
   Object.freeze({
     name: "Amazon ElastiCache",
     name: "Amazon ElastiCache",
@@ -110,6 +128,11 @@ export const SUPPORTED_DATABASE_TEMPLATES: DatabaseTemplate[] = [
       },
       },
     ],
     ],
     formTitle: "Create an ElastiCache Redis instance",
     formTitle: "Create an ElastiCache Redis instance",
+    creationStateProgression: [
+      DATABASE_STATE_CREATING,
+      DATABASE_STATE_MODIFYING,
+      DATABASE_STATE_AVAILABLE,
+    ],
   }),
   }),
   Object.freeze({
   Object.freeze({
     name: "Amazon ElastiCache",
     name: "Amazon ElastiCache",
@@ -121,5 +144,6 @@ export const SUPPORTED_DATABASE_TEMPLATES: DatabaseTemplate[] = [
     disabled: true,
     disabled: true,
     instanceTiers: [],
     instanceTiers: [],
     formTitle: "Create an ElastiCache Memcached instance",
     formTitle: "Create an ElastiCache Memcached instance",
+    creationStateProgression: [],
   }),
   }),
 ];
 ];

+ 1 - 1
dashboard/src/main/home/database-dashboard/forms/DatabaseForm.tsx

@@ -60,7 +60,7 @@ const DatabaseForm: React.FC<Props> = ({
     }
     }
     try {
     try {
       await createDatabase(data);
       await createDatabase(data);
-      history.push(`/databases`);
+      history.push(`/databases/${data.name}`);
     } catch (err) {
     } catch (err) {
       const errorMessage =
       const errorMessage =
         axios.isAxiosError(err) && err.response?.data?.error
         axios.isAxiosError(err) && err.response?.data?.error

+ 3 - 0
dashboard/src/main/home/database-dashboard/tags/EngineTag.tsx

@@ -27,6 +27,9 @@ const EngineTag: React.FC<Props> = ({ engine, heightPixels = 13 }) => {
           .with({ name: "REDIS" }, () => (
           .with({ name: "REDIS" }, () => (
             <Icon src={redis} height={`${heightPixels}px`} />
             <Icon src={redis} height={`${heightPixels}px`} />
           ))
           ))
+          .with({ name: "AURORA-POSTGRES" }, () => (
+            <Icon src={postgresql} height={`${heightPixels}px`} />
+          ))
           .otherwise(() => null)}
           .otherwise(() => null)}
       </IconContainer>
       </IconContainer>
       <Spacer inline x={0.5} />
       <Spacer inline x={0.5} />

+ 1 - 1
go.mod

@@ -83,7 +83,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.86
+	github.com/porter-dev/api-contracts v0.2.89
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 0
go.sum

@@ -1525,6 +1525,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
 github.com/porter-dev/api-contracts v0.2.86 h1:uH6beKklp1YCzLBrtuuFVDwWfvQnlBWbxZBtDsO3+AI=
 github.com/porter-dev/api-contracts v0.2.86 h1:uH6beKklp1YCzLBrtuuFVDwWfvQnlBWbxZBtDsO3+AI=
 github.com/porter-dev/api-contracts v0.2.86/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/api-contracts v0.2.86/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.89 h1:ugkZr7aaANWdRFbkpeEHReyG1nWr31gx9c9dPvgkKMw=
+github.com/porter-dev/api-contracts v0.2.89/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=