Skip to content

Commit

Permalink
[POR-2200] Beginning work of making db dashboard look like app dashbo…
Browse files Browse the repository at this point in the history
…ard (#4164)

Co-authored-by: Stefan McShane <[email protected]>
  • Loading branch information
Feroze Mohideen and stefanmcshane authored Jan 18, 2024
1 parent 1ba29af commit 4b9d46e
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 169 deletions.
1 change: 1 addition & 0 deletions api/server/handlers/datastore/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func (c *GetDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
datastore := datastores[0]
datastore.Type = datastoreRecord.Type
datastore.Engine = datastoreRecord.Engine
datastore.CreatedAtUTC = datastoreRecord.CreatedAt

resp.Datastore = datastore

Expand Down
11 changes: 8 additions & 3 deletions api/server/handlers/datastore/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package datastore
import (
"context"
"net/http"
"time"

"connectrpc.com/connect"
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
Expand Down Expand Up @@ -59,6 +60,9 @@ type Datastore struct {

// Status is the status of the datastore
Status string `json:"status,omitempty"`

// CreatedAtUTC is the time the datastore was created in UTC
CreatedAtUTC time.Time `json:"created_at"`
}

// ListDatastoresHandler is a struct for listing all datastores for a given project
Expand Down Expand Up @@ -98,9 +102,10 @@ func (h *ListDatastoresHandler) ServeHTTP(w http.ResponseWriter, r *http.Request

for _, datastore := range datastores {
datastoreList = append(datastoreList, Datastore{
Name: datastore.Name,
Type: datastore.Type,
Engine: datastore.Engine,
Name: datastore.Name,
Type: datastore.Type,
Engine: datastore.Engine,
CreatedAtUTC: datastore.CreatedAt,
})
}

Expand Down
50 changes: 50 additions & 0 deletions dashboard/src/components/porter/StatusDot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useMemo } from "react";
import styled from "styled-components";
import { match } from "ts-pattern";

type StatusDotProps = {
status: "available" | "pending" | "failing";
heightPixels?: number;
};

const StatusDot: React.FC<StatusDotProps> = ({ status, heightPixels = 7 }) => {
const color = useMemo(() => {
return match(status)
.with("available", () => "#38a88a")
.with("pending", () => "#FFA500")
.with("failing", () => "#ff0000")
.exhaustive();
}, [status]);
return <StyledStatusDot color={color} height={heightPixels} />;
};

export default StatusDot;

const StyledStatusDot = styled.div<{ color: string; height: number }>`
min-width: ${(props) => props.height}px;
max-width: ${(props) => props.height}px;
height: ${(props) => props.height}px;
border-radius: 50%;
margin-right: 10px;
background: ${(props) => props.color};
box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);
transform: scale(1);
animation: pulse 2s infinite;
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
`;
7 changes: 6 additions & 1 deletion dashboard/src/lib/databases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ export const datastoreValidator = z.object({
name: z.string(),
type: z.string(),
engine: z.string(),
created_at: z.string().default(""),
status: z.string().default(""),
metadata: datastoreMetadataValidator.array().default([]),
env: datastoreEnvValidator.optional(),
connection_string: z.string().default(""),
});

export type ClientDatastore = z.infer<typeof datastoreValidator>;
export type SerializedDatastore = z.infer<typeof datastoreValidator>;

export type ClientDatastore = SerializedDatastore & {
template: DatabaseTemplate;
};

export const datastoreListResponseValidator = z.object({
datastores: datastoreValidator.array(),
Expand Down
18 changes: 15 additions & 3 deletions dashboard/src/lib/hooks/useDatabaseList.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useContext } from "react";
import { useQuery } from "@tanstack/react-query";

import { SUPPORTED_DATABASE_TEMPLATES } from "main/home/database-dashboard/constants";
import {
datastoreListResponseValidator,
type ClientDatastore,
type DatabaseTemplate,
type SerializedDatastore,
} from "lib/databases/types";

import api from "shared/api";
import { Context } from "shared/Context";
import { valueExists } from "shared/util";

type DatabaseListType = {
datastores: ClientDatastore[];
datastores: Array<SerializedDatastore & { template: DatabaseTemplate }>;
isLoading: boolean;
};
export const useDatabaseList = (): DatabaseListType => {
Expand All @@ -34,7 +37,16 @@ export const useDatabaseList = (): DatabaseListType => {
const parsed = await datastoreListResponseValidator.parseAsync(
response.data
);
return parsed.datastores;
return parsed.datastores
.map((d) => {
const template = SUPPORTED_DATABASE_TEMPLATES.find(
(t) => t.type === d.type && t.engine.name === d.engine
);

// filter out this datastore if it is a type we do not recognize
return template ? { ...d, template } : null;
})
.filter(valueExists);
},
{
enabled: !!currentProject?.id && currentProject.id !== -1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import api from "shared/api";
import { Context } from "shared/Context";
import notFound from "assets/not-found.png";

import { SUPPORTED_DATABASE_TEMPLATES } from "./constants";

type DatabaseContextType = {
datastore: ClientDatastore;
projectId: number;
Expand Down Expand Up @@ -59,7 +61,21 @@ export const DatabaseContextProvider: React.FC<
const results = await z
.object({ datastore: datastoreValidator })
.parseAsync(response.data);
return results.datastore;

const datastore = results.datastore;
const matchingTemplate = SUPPORTED_DATABASE_TEMPLATES.find(
(t) => t.type === datastore.type && t.engine.name === datastore.engine
);

// this datastore is a type we do not recognize
if (!matchingTemplate) {
return;
}

return {
...results.datastore,
template: matchingTemplate,
};
},
{
enabled: paramsExist,
Expand Down
144 changes: 34 additions & 110 deletions dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import { useDatabaseList } from "lib/hooks/useDatabaseList";

import { Context } from "shared/Context";
import { search } from "shared/search";
import { readableDate } from "shared/string_utils";
import database from "assets/database.svg";
import grid from "assets/grid.png";
import list from "assets/list.png";
import loading from "assets/loading.gif";
import notFound from "assets/not-found.png";
import healthy from "assets/status-healthy.png";
import time from "assets/time.png";

import { getTemplateEngineDisplayName, getTemplateIcon } from "./constants";
import EngineTag from "./tags/EngineTag";

const DatabaseDashboard: React.FC = () => {
const { currentCluster } = useContext(Context);
Expand All @@ -47,35 +48,6 @@ const DatabaseDashboard: React.FC = () => {
return _.sortBy(filteredBySearch, ["name"]);
}, [datastores, searchValue]);

const renderStatusIcon = (status: string): JSX.Element => {
switch (status) {
case "available":
return <StatusIcon src={healthy} />;
case "":
return <></>;
case "error":
return (
<StatusText>
<StatusWrapper success={false}>
<Status src={loading} />
{"Creating database"}
</StatusWrapper>
</StatusText>
);
case "updating":
return (
<StatusText>
<StatusWrapper success={false}>
<Status src={loading} />
{"Creating database"}
</StatusWrapper>
</StatusText>
);
default:
return <></>;
}
};

const renderContents = (): JSX.Element => {
if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
return <ClusterProvisioningPlaceholder />;
Expand Down Expand Up @@ -169,33 +141,25 @@ const DatabaseDashboard: React.FC = () => {
<GridList>
{(filteredDatabases ?? []).map(
(datastore: ClientDatastore, i: number) => {
const templateIcon = getTemplateIcon(
datastore.type,
datastore.engine
);
const templateDisplayName = getTemplateEngineDisplayName(
datastore.engine
);
return (
<Link to={`/databases/${datastore.name}`} key={i}>
<Block>
<Container row spaced>
<Container row>
<Icon src={templateIcon} />
<Icon src={datastore.template.icon} />
<Text size={14}>{datastore.name}</Text>
</Container>
<MidIcon src={healthy} height="16px" />
</Container>
{templateDisplayName && (
<>
<Spacer y={1} />
<Container row>
<Tag hoverable={false}>
<Text size={13}>{templateDisplayName}</Text>
</Tag>
</Container>
</>
)}
<Container row>
<EngineTag engine={datastore.template.engine} />
</Container>
<Container row>
<SmallIcon opacity="0.4" src={time} />
<Text size={13} color="#ffffff44">
{readableDate(datastore.created_at)}
</Text>
</Container>
</Block>
</Link>
);
Expand All @@ -206,31 +170,25 @@ const DatabaseDashboard: React.FC = () => {
<List>
{(filteredDatabases ?? []).map(
(datastore: ClientDatastore, i: number) => {
const templateIcon = getTemplateIcon(
datastore.type,
datastore.engine
);
const templateDisplayName = getTemplateEngineDisplayName(
datastore.engine
);
return (
<Row to={`/databases/${datastore.name}`} key={i}>
<Container row>
<MidIcon src={templateIcon} />
<Text size={14}>{datastore.name}</Text>
<Spacer inline x={1} />
<Container row spaced>
<Container row>
<MidIcon src={datastore.template.icon} />
<Text size={14}>{datastore.name}</Text>
</Container>
<MidIcon src={healthy} height="16px" />
</Container>
<Spacer height="15px" />
<Spacer y={0.5} />
<Container row>
{templateDisplayName && (
<>
<Spacer inline x={0.5} />
<Tag hoverable={false}>
<Text size={13}>{templateDisplayName}</Text>
</Tag>
</>
)}
<EngineTag engine={datastore.template.engine} />
<Spacer inline x={1} />
<Container>
<SmallIcon opacity="0.4" src={time} />
<Text size={13} color="#ffffff44">
{readableDate(datastore.created_at)}
</Text>
</Container>
</Container>
</Row>
);
Expand Down Expand Up @@ -281,20 +239,13 @@ const List = styled.div`
overflow: hidden;
`;

const StatusIcon = styled.img`
position: absolute;
top: 20px;
right: 20px;
height: 18px;
`;

const Icon = styled.img`
height: 20px;
margin-right: 13px;
`;

const Block = styled.div`
height: 120px;
height: 150px;
flex-direction: column;
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -353,37 +304,10 @@ const StyledAppDashboard = styled.div`
height: 100%;
`;

const StatusText = styled.div`
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
justify-content: center;
`;

const StatusWrapper = styled.div<{
success?: boolean;
}>`
display: flex;
line-height: 1.5;
align-items: center;
font-family: "Work Sans", sans-serif;
font-size: 13px;
color: #ffffff55;
margin-left: 15px;
text-overflow: ellipsis;
animation-fill-mode: forwards;
> i {
font-size: 18px;
margin-right: 10px;
float: left;
color: ${(props) => (props.success ? "#4797ff" : "#fcba03")};
}
`;
const Status = styled.img`
width: 15px;
height: 15px;
margin-right: 9px;
margin-bottom: 0px;
const SmallIcon = styled.img<{ opacity?: string; height?: string }>`
margin-left: 2px;
height: ${(props) => props.height || "14px"};
opacity: ${(props) => props.opacity || 1};
filter: grayscale(100%);
margin-right: 10px;
`;
Loading

0 comments on commit 4b9d46e

Please sign in to comment.