diff --git a/dashboard/src/components/porter/Image.tsx b/dashboard/src/components/porter/Image.tsx index 3ac463524e..1ce02c6ced 100644 --- a/dashboard/src/components/porter/Image.tsx +++ b/dashboard/src/components/porter/Image.tsx @@ -34,7 +34,7 @@ const StyledIcon = styled.img<{ opacity?: number; additionalStyles?: string; }>` - height: ${props => props.size || 20}px; - opacity: ${props => props.opacity || 1}; - ${props => props.additionalStyles ? props.additionalStyles : ""} -`; \ No newline at end of file + height: ${(props) => props.size || 20}px; + opacity: ${(props) => props.opacity || 1}; + ${(props) => (props.additionalStyles ? props.additionalStyles : "")} +`; diff --git a/dashboard/src/components/porter/Select.tsx b/dashboard/src/components/porter/Select.tsx index b9b266e3ad..b5e06d42b4 100644 --- a/dashboard/src/components/porter/Select.tsx +++ b/dashboard/src/components/porter/Select.tsx @@ -1,14 +1,16 @@ -import React, { useEffect, useRef, useState } from "react"; +import React from "react"; import styled from "styled-components"; + import arrow from "assets/arrow-down.svg"; + import Container from "./Container"; type Props = { width?: string; - options: Array<{ + options: Array<{ label: string; value: string; - icon?: HTMLImageElement | string; + icon?: string; disabled?: boolean; }>; label?: string | React.ReactNode; @@ -23,48 +25,46 @@ type Props = { }; const Select: React.FC = ({ - width, options, label, labelColor, - height, error, children, disabled, value, setValue, prefix, + width = "200px", + height = "35px", }) => { - const prefixRef = useRef(null); - return ( {label && } - {prefix} - - {options.map((option) => { - if (option.value === value) { - return ( - - {option.icon && } - {option.label} - - ) - } - return null; - })} - + {prefix} + + {options.map((option) => { + if (option.value === value) { + return ( + + {option.icon && } + {option.label} + + ); + } + return null; + })} + { - setValue(e.target.value); + setValue?.(e.target.value); }} width={width} height={height} hasError={(error && true) || error === ""} - disabled={disabled ? disabled : false} + disabled={disabled || false} value={value} > {options.map((option, i) => { @@ -134,9 +134,9 @@ const Block = styled.div<{ width: ${(props) => props.width || "200px"}; `; -const Label = styled.div<{color?: string}>` +const Label = styled.div<{ color?: string }>` font-size: 13px; - color: ${({color = "#aaaabb"}) => color}; + color: ${({ color = "#aaaabb" }) => color}; margin-bottom: 10px; `; @@ -156,9 +156,9 @@ const Error = styled.div` const SelectWrapper = styled.div` position: relative; background: ${(props) => props.theme.fg}; - border: 1px solid ${(props) => (props.hasError ? "#ff3b62" : "#494b4f")}; + border: 1px solid #494b4f; :hover { - border: 1px solid ${(props) => (props.hasError ? "#ff3b62" : "#7a7b80")}; + border: 1px solid #7a7b80; } z-index: 0; display: flex; @@ -172,12 +172,10 @@ const StyledSelect = styled.select<{ width: string; height: string; hasError: boolean; - paddingLeft: number; }>` - height: ${(props) => props.height || "35px"}; + height: ${(props) => props.height}; padding: 5px 10px; - padding-left: ${(props) => props.paddingLeft}px; - width: ${(props) => props.width || "200px"}; + width: ${(props) => props.width}; color: #ffffff; font-size: 13px; outline: none; diff --git a/dashboard/src/lib/databases/types.ts b/dashboard/src/lib/databases/types.ts index c9a11984a6..f911db5275 100644 --- a/dashboard/src/lib/databases/types.ts +++ b/dashboard/src/lib/databases/types.ts @@ -21,8 +21,8 @@ export type DatastoreMetadataWithSource = z.infer< export const datastoreValidator = z.object({ name: z.string(), - type: z.string(), - engine: z.string(), + type: z.enum(["RDS", "ELASTICACHE"]), + engine: z.enum(["POSTGRES", "AURORA-POSTGRES", "REDIS", "MEMCACHED"]), created_at: z.string().default(""), metadata: datastoreMetadataValidator.array().default([]), env: datastoreEnvValidator.optional(), @@ -70,11 +70,10 @@ export type CloudProviderDatastore = z.infer< typeof cloudProviderDatastoreSchema >; -export type DatabaseEngine = - | typeof DATABASE_ENGINE_POSTGRES - | typeof DATABASE_ENGINE_AURORA_POSTGRES - | typeof DATABASE_ENGINE_REDIS - | typeof DATABASE_ENGINE_MEMCACHED; +export type DatabaseEngine = { + name: z.infer["engine"]; + displayName: string; +}; export const DATABASE_ENGINE_POSTGRES = { name: "POSTGRES" as const, displayName: "PostgreSQL", @@ -92,9 +91,7 @@ export const DATABASE_ENGINE_MEMCACHED = { displayName: "Memcached", }; -export type DatabaseType = - | typeof DATABASE_TYPE_RDS - | typeof DATABASE_TYPE_ELASTICACHE; +export type DatabaseType = z.infer["type"]; export const DATABASE_TYPE_RDS = "RDS" as const; export const DATABASE_TYPE_ELASTICACHE = "ELASTICACHE" as const; @@ -102,28 +99,28 @@ export type DatabaseState = { state: z.infer["status"]; displayName: string; }; -export const DATABASE_STATE_CREATING = { - state: "CREATING" as const, +export const DATABASE_STATE_CREATING: DatabaseState = { + state: "CREATING", displayName: "Creating", }; -export const DATABASE_STATE_CONFIGURING_LOG_EXPORTS = { - state: "CONFIGURING_LOG_EXPORTS" as const, +export const DATABASE_STATE_CONFIGURING_LOG_EXPORTS: DatabaseState = { + state: "CONFIGURING_LOG_EXPORTS", displayName: "Configuring log exports", }; -export const DATABASE_STATE_MODIFYING = { - state: "MODIFYING" as const, +export const DATABASE_STATE_MODIFYING: DatabaseState = { + state: "MODIFYING", displayName: "Modifying", }; -export const DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING = { - state: "CONFIGURING_ENHANCED_MONITORING" as const, +export const DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING: DatabaseState = { + state: "CONFIGURING_ENHANCED_MONITORING", displayName: "Configuring enhanced monitoring", }; -export const DATABASE_STATE_BACKING_UP = { - state: "BACKING_UP" as const, +export const DATABASE_STATE_BACKING_UP: DatabaseState = { + state: "BACKING_UP", displayName: "Backing up", }; -export const DATABASE_STATE_AVAILABLE = { - state: "AVAILABLE" as const, +export const DATABASE_STATE_AVAILABLE: DatabaseState = { + state: "AVAILABLE", displayName: "Finishing provision", }; diff --git a/dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx b/dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx index da61913afd..b3640a5d02 100644 --- a/dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx +++ b/dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx @@ -9,8 +9,10 @@ import Button from "components/porter/Button"; import Container from "components/porter/Container"; import DashboardPlaceholder from "components/porter/DashboardPlaceholder"; import Fieldset from "components/porter/Fieldset"; +import Image from "components/porter/Image"; import PorterLink from "components/porter/Link"; import SearchBar from "components/porter/SearchBar"; +import Select from "components/porter/Select"; import Spacer from "components/porter/Spacer"; import StatusDot from "components/porter/StatusDot"; import Text from "components/porter/Text"; @@ -22,12 +24,14 @@ import { useDatabaseList } from "lib/hooks/useDatabaseList"; import { Context } from "shared/Context"; import { search } from "shared/search"; import { readableDate } from "shared/string_utils"; +import engine from "assets/computer-chip.svg"; import database from "assets/database.svg"; import grid from "assets/grid.png"; import list from "assets/list.png"; import notFound from "assets/not-found.png"; import time from "assets/time.png"; +import { getDatastoreIcon, getEngineIcon } from "./icons"; import EngineTag from "./tags/EngineTag"; const DatabaseDashboard: React.FC = () => { @@ -35,6 +39,12 @@ const DatabaseDashboard: React.FC = () => { const [searchValue, setSearchValue] = useState(""); const [view, setView] = useState<"grid" | "list">("grid"); + const [typeFilter, setTypeFilter] = useState<"all" | "RDS" | "ELASTICACHE">( + "all" + ); + const [engineFilter, setEngineFilter] = useState< + "all" | "POSTGRES" | "AURORA-POSTGRES" | "REDIS" + >("all"); const { datastores, isLoading } = useDatabaseList(); @@ -44,8 +54,28 @@ const DatabaseDashboard: React.FC = () => { isCaseSensitive: false, }); - return _.sortBy(filteredBySearch, ["name"]); - }, [datastores, searchValue]); + const sortedFilteredBySearch = _.sortBy(filteredBySearch, ["name"]); + + const filteredByDatastoreType = sortedFilteredBySearch.filter( + (datastore: ClientDatastore) => { + if (typeFilter === "all") { + return true; + } + return datastore.type === typeFilter; + } + ); + + const filteredByEngine = filteredByDatastoreType.filter( + (datastore: ClientDatastore) => { + if (engineFilter === "all") { + return true; + } + return datastore.template.engine.name === engineFilter; + } + ); + + return filteredByEngine; + }, [datastores, searchValue, typeFilter, engineFilter]); const renderContents = (): JSX.Element => { if (currentCluster?.status === "UPDATING_UNAVAILABLE") { @@ -86,6 +116,83 @@ const DatabaseDashboard: React.FC = () => { return ( <> + { + if ( + value === "all" || + value === "POSTGRES" || + value === "AURORA-POSTGRES" || + value === "REDIS" + ) { + setEngineFilter(value); + } + }} + prefix={ + + + + Engine + + } + /> + { @@ -119,9 +226,9 @@ const DatabaseDashboard: React.FC = () => { true } height="30px" - width="140px" + width="70px" > - add New database + add New @@ -131,7 +238,9 @@ const DatabaseDashboard: React.FC = () => {
- No matching databases were found. + + No databases matching filters were found. +
) : isLoading ? ( diff --git a/dashboard/src/main/home/database-dashboard/icons.tsx b/dashboard/src/main/home/database-dashboard/icons.tsx index 5971076ef1..7586c95855 100644 --- a/dashboard/src/main/home/database-dashboard/icons.tsx +++ b/dashboard/src/main/home/database-dashboard/icons.tsx @@ -1,16 +1,33 @@ import { + DATABASE_ENGINE_AURORA_POSTGRES, + DATABASE_ENGINE_POSTGRES, + DATABASE_ENGINE_REDIS, DATABASE_TYPE_ELASTICACHE, DATABASE_TYPE_RDS, } from "lib/databases/types"; import awsRDS from "assets/amazon-rds.png"; import awsElasticache from "assets/aws-elasticache.png"; +import engine from "assets/computer-chip.svg"; +import database from "assets/database.svg"; +import postgresql from "assets/postgresql.svg"; +import redis from "assets/redis.svg"; -export const datastoreIcons: Record = { +const datastoreIcons: Record = { [DATABASE_TYPE_ELASTICACHE]: awsElasticache, [DATABASE_TYPE_RDS]: awsRDS, }; +const engineIcons: Record = { + [DATABASE_ENGINE_POSTGRES.name]: postgresql, + [DATABASE_ENGINE_AURORA_POSTGRES.name]: postgresql, + [DATABASE_ENGINE_REDIS.name]: redis, +}; + export const getDatastoreIcon = (datastoreType: string): string => { - return datastoreIcons[datastoreType] ?? awsRDS; + return datastoreIcons[datastoreType] ?? database; +}; + +export const getEngineIcon = (engineName: string): string => { + return engineIcons[engineName] ?? engine; };