From 1be064d7ae82fe011bae6b8b3d9b782da174eb04 Mon Sep 17 00:00:00 2001 From: Akshay Shekhawat Date: Sat, 30 Jul 2022 21:44:06 -0700 Subject: [PATCH] work on endpoint page --- frontend/package.json | 2 + .../src/components/Endpoint/PIIDataList.tsx | 69 ++++++++ .../src/components/Endpoint/UsageChart.tsx | 54 ++++++ frontend/src/components/Endpoint/index.tsx | 116 +++++++++++-- frontend/src/components/EndpointList/List.tsx | 163 ++++-------------- frontend/src/components/Sidebar/index.tsx | 1 - frontend/src/components/utils/Card.tsx | 48 +++++- frontend/src/components/utils/TableUtils.tsx | 71 ++++++++ frontend/src/constants.ts | 8 + .../src/pages/endpoint/[endpointUUID].tsx | 3 +- frontend/src/testData.ts | 39 ++++- frontend/yarn.lock | 10 ++ 12 files changed, 422 insertions(+), 162 deletions(-) create mode 100644 frontend/src/components/Endpoint/PIIDataList.tsx create mode 100644 frontend/src/components/Endpoint/UsageChart.tsx create mode 100644 frontend/src/components/utils/TableUtils.tsx diff --git a/frontend/package.json b/frontend/package.json index c0b9d8aa..944511c1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,9 +13,11 @@ "@emotion/styled": "^11.9.0", "@react-icons/all-files": "^4.1.0", "chakra-react-select": "^4.1.4", + "chart.js": "^3.8.2", "framer-motion": "^6.3.0", "next": "latest", "react": "^18.2.0", + "react-chartjs-2": "^4.3.1", "react-data-table-component": "^7.5.2", "react-dom": "^18.2.0", "styled-components": "^5.3.5" diff --git a/frontend/src/components/Endpoint/PIIDataList.tsx b/frontend/src/components/Endpoint/PIIDataList.tsx new file mode 100644 index 00000000..8e5dd2cb --- /dev/null +++ b/frontend/src/components/Endpoint/PIIDataList.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { useColorMode, Code, Badge } from "@chakra-ui/react"; +import { PIIField } from "../../types"; +import DataTable, { TableColumn } from "react-data-table-component"; +import { getCustomStyles, rowStyles } from "../utils/TableUtils"; +import { RISK_TO_COLOR } from "../../constants"; + +interface PIIDataListProps { + PIIFields: PIIField[]; +} + +const PIIDataList: React.FC = React.memo(({ PIIFields }) => { + const colorMode = useColorMode(); + const columns: TableColumn[] = [ + { + name: "Risk Score", + sortable: true, + selector: (row: PIIField) => row.risk || "", + cell: (row: PIIField) => ( + + {row.risk} + + ), + id: "riskScore", + grow: 1, + }, + { + name: "Data Type", + sortable: true, + selector: (row: PIIField) => row.dataType || "", + id: "dataType", + grow: 1, + }, + { + name: "Data Path", + sortable: true, + selector: (row: PIIField) => row.dataPath, + cell: (row: PIIField) => ( + + {row.dataPath} + + ), + id: "dataPath", + grow: 2, + }, + { + name: "Date Identified", + sortable: true, + selector: (row: PIIField) => row.dateIdentified || "", + id: "dateIdentified", + grow: 2, + }, + ]; + return ( + + ); +}); + +export default PIIDataList; diff --git a/frontend/src/components/Endpoint/UsageChart.tsx b/frontend/src/components/Endpoint/UsageChart.tsx new file mode 100644 index 00000000..8a10bb80 --- /dev/null +++ b/frontend/src/components/Endpoint/UsageChart.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +} from "chart.js"; +import { Line } from "react-chartjs-2"; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); + +export const options = { + responsive: true, + plugins: { + legend: { + display: false, + }, + }, +}; + +const labels = ["January", "February", "March", "April", "May", "June", "July"]; + +export const data = { + labels, + datasets: [ + { + data: labels.map(() => Math.floor(Math.random() * 1000)), + borderColor: "rgb(255, 99, 132)", + backgroundColor: "rgba(255, 99, 132, 0.5)", + }, + ], +}; + +interface EndpointUsageChartProps {} + +const EndpointUsageChart: React.FC = React.memo( + ({}) => { + return ; + } +); + +export default EndpointUsageChart; diff --git a/frontend/src/components/Endpoint/index.tsx b/frontend/src/components/Endpoint/index.tsx index df4ff449..5ecb10a5 100644 --- a/frontend/src/components/Endpoint/index.tsx +++ b/frontend/src/components/Endpoint/index.tsx @@ -1,20 +1,54 @@ import React from "react"; +import NextLink from "next/link"; import { BiInfoCircle } from "@react-icons/all-files/bi/BiInfoCircle"; import { BsFillLockFill } from "@react-icons/all-files/bs/BsFillLockFill"; +import { GrStackOverflow } from "@react-icons/all-files/gr/GrStackOverflow"; +import { TiFlowSwitch } from "@react-icons/all-files/ti/TiFlowSwitch"; import { FaBell } from "@react-icons/all-files/fa/FaBell"; -import { Badge, Code, GridItem, HStack, VStack } from "@chakra-ui/react"; +import { + Box, + Badge, + Code, + GridItem, + HStack, + VStack, + Text, + useColorModeValue, + Tabs, + TabList, + Tab, + TabPanels, + TabPanel, +} from "@chakra-ui/react"; import { Endpoint } from "../../types"; -import { CardWithHeader } from "../utils/Card"; -import { METHOD_TO_COLOR } from "../../constants"; +import { + CardWithHeader, + DataAttribute, + DataHeading, + SectionHeader, +} from "../utils/Card"; +import { METHOD_TO_COLOR, RISK_TO_COLOR } from "../../constants"; +import PIIDataList from "./PIIDataList"; +import EndpointUsageChart from "./UsageChart"; interface EndpointPageProps { endpoint: Endpoint; } const EndpointPage: React.FC = React.memo(({ endpoint }) => { + const headerColor = useColorModeValue( + "rgb(179, 181, 185)", + "rgb(91, 94, 109)" + ); return ( - - + + + + + Endpoints + + + = React.memo(({ endpoint }) => { - - - - - - - - - - - + + Host + {endpoint.host} + + + Environment + {endpoint.environment} + + + Risk Score + + {endpoint.riskScore} + + + + First Detected + {endpoint.firstDetected} + + + Last Active + {endpoint.lastActive} + + + Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/frontend/src/components/EndpointList/List.tsx b/frontend/src/components/EndpointList/List.tsx index 0f683f2a..108d63ad 100644 --- a/frontend/src/components/EndpointList/List.tsx +++ b/frontend/src/components/EndpointList/List.tsx @@ -1,24 +1,11 @@ import React from "react"; -import { - Badge, - Box, - Code, - Skeleton, - useColorMode, - ColorMode, - HStack, -} from "@chakra-ui/react"; +import { Badge, Box, Code, useColorMode, HStack } from "@chakra-ui/react"; import { useRouter } from "next/router"; import EmptyView from "../utils/EmptyView"; -import _ from "lodash"; -import DataTable, { - Media, - SortOrder, - TableColumn, - TableStyles, -} from "react-data-table-component"; -import { Endpoint, RiskScore } from "../../types"; -import { METHOD_TO_COLOR } from "../../constants"; +import DataTable, { SortOrder, TableColumn } from "react-data-table-component"; +import { Endpoint } from "../../types"; +import { METHOD_TO_COLOR, RISK_TO_COLOR } from "../../constants"; +import { getCustomStyles, rowStyles, SkeletonCell } from "../utils/TableUtils"; const PAGE_SIZE = 10; @@ -37,70 +24,6 @@ interface TableLoaderProps { totalCount: number; } -const conditionalRowStyles = [ - { - when: (row: TableColumn) => row.name !== null, - style: { - backgroundColor: "rgba(63, 195, 128, 0.9)", - color: "white", - "&:hover": { - cursor: "pointer", - }, - }, - }, -]; - -const getCustomStyles = (colorMode: ColorMode): TableStyles => { - const headerBg = - colorMode == "light" ? "rgb(252, 252, 252)" : "rgb(17, 19, 23)"; - const headerTextColor = - colorMode == "light" ? "rgb(163, 165, 170)" : "rgb(98, 100, 116)"; - const textColor = colorMode == "light" ? "black" : "white"; - const rowColor = colorMode == "light" ? "white" : "rgb(21, 23, 27)"; - const hoverRowColor = - colorMode == "light" ? "rgb(252, 252, 252)" : "rgb(24, 26, 30)"; - return { - rows: { - style: { - background: rowColor, - color: textColor, - minHeight: "64px", - fontWeight: "500", - "&:hover": { - cursor: "pointer", - background: hoverRowColor, - }, - }, - }, - headRow: { - style: { - background: headerBg, - color: headerTextColor, - }, - }, - pagination: { - style: { - background: headerBg, - color: textColor, - }, - pageButtonsStyle: { - color: textColor, - fill: textColor, - "&:disabled": { - fill: textColor, - color: textColor, - }, - }, - }, - } as TableStyles; -}; - -const riskToColor = { - [RiskScore.LOW]: "gray", - [RiskScore.MEDIUM]: "orange", - [RiskScore.HIGH]: "red", -}; - const TableLoader: React.FC = ({ currentPage, totalCount, @@ -108,63 +31,45 @@ const TableLoader: React.FC = ({ const colorMode = useColorMode(); const loadingColumns: TableColumn[] = [ { - name: "Table", - selector: () => "", - cell: () => ( - - - - ), - id: "table_id", + name: "Risk Score", + id: "riskScore", + grow: 1, + }, + { + name: "Path", + id: "pathMethod", + grow: 3, }, { - name: "Dataset", - selector: () => "", - cell: () => ( - - - - ), - id: "table_dataset", - hide: Media.LG, + name: "Environment", + id: "environment", + grow: 1, }, { - name: "Project", - selector: () => "", - cell: () => ( - - - - ), - id: "table_project", - hide: Media.MD, + name: "Host", + id: "host", + grow: 1, }, { - name: "Owner", - selector: () => "", - cell: () => ( - - - - ), - id: "owner", + name: "First Detected", + id: "firstDetected", + grow: 2, }, { - name: "Datasource", - selector: () => "", - cell: () => ( - - - - ), - id: "datasource_name", + name: "Last Active", + id: "lastActive", + grow: 2, }, - ]; + ].map((e) => ({ + ...e, + sortable: true, + cell: (row: Endpoint) => , + })); return ( = React.memo( {row.riskScore} @@ -238,7 +143,7 @@ const List: React.FC = React.memo( {row.path} ), - id: "method", + id: "pathMethod", grow: 3, }, { @@ -280,7 +185,7 @@ const List: React.FC = React.memo( const getTable = () => ( = - React.memo(({ text, sym }) => ( - +type DataHeadingProps = TextProps; + +export const DataHeading: React.FC = React.memo( + ({ children, ...props }) => { + const color = useColorModeValue("rgb(102, 105, 117)", "rgb(116, 120, 138)"); + return ( + + {children} + + ); + } +); + +type DataAttributeProps = TextProps; + +export const DataAttribute: React.FC = React.memo( + ({ children, ...props }) => { + const color = useColorModeValue("black", "white"); + return ( + + {children} + + ); + } +); + +export const SectionHeader: React.FC<{ + text: string; + sym: IconType; +}> = React.memo(({ text, sym }) => { + return ( + {sym({})} {text} - )); + ); +}); export const Card: React.FC<{ children: React.ReactNode }> = React.memo( ({ children }) => { diff --git a/frontend/src/components/utils/TableUtils.tsx b/frontend/src/components/utils/TableUtils.tsx new file mode 100644 index 00000000..ce21ba4b --- /dev/null +++ b/frontend/src/components/utils/TableUtils.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { Box, Skeleton, useColorModeValue } from "@chakra-ui/react"; +import { ColorMode } from "@chakra-ui/react"; +import { TableStyles } from "react-data-table-component"; + +export const getCustomStyles = (colorMode: ColorMode): TableStyles => { + const headerBg = + colorMode == "light" ? "rgb(252, 252, 252)" : "rgb(17, 19, 23)"; + const headerTextColor = + colorMode == "light" ? "rgb(163, 165, 170)" : "rgb(98, 100, 116)"; + const textColor = colorMode == "light" ? "black" : "white"; + const rowColor = colorMode == "light" ? "white" : "rgb(21, 23, 27)"; + const hoverRowColor = + colorMode == "light" ? "rgb(252, 252, 252)" : "rgb(24, 26, 30)"; + return { + rows: { + style: { + background: rowColor, + color: textColor, + minHeight: "64px", + fontWeight: "500", + "&:hover": { + cursor: "pointer", + background: hoverRowColor, + }, + }, + }, + headRow: { + style: { + background: headerBg, + color: headerTextColor, + }, + }, + pagination: { + style: { + background: headerBg, + color: textColor, + }, + pageButtonsStyle: { + color: textColor, + fill: textColor, + "&:disabled": { + fill: textColor, + color: textColor, + }, + }, + }, + } as TableStyles; +}; + +export const SkeletonCell = React.memo(() => { + const startColor = useColorModeValue("gray.50", "gray.700"); + const endColor = useColorModeValue("gray.200", "gray.800"); + return ( + + + + ); +}); + +export const rowStyles = [ + { + style: { + backgroundColor: "rgba(63, 195, 128, 0.9)", + color: "white", + "&:hover": { + cursor: "pointer", + }, + }, + }, +]; diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 4816a414..e11a077e 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,4 +1,12 @@ +import { RiskScore } from "./types"; + export const METHOD_TO_COLOR = { GET: "green", POST: "orange", }; + +export const RISK_TO_COLOR = { + [RiskScore.LOW]: "gray", + [RiskScore.MEDIUM]: "orange", + [RiskScore.HIGH]: "red", +}; diff --git a/frontend/src/pages/endpoint/[endpointUUID].tsx b/frontend/src/pages/endpoint/[endpointUUID].tsx index 8b0dfa50..8129a830 100644 --- a/frontend/src/pages/endpoint/[endpointUUID].tsx +++ b/frontend/src/pages/endpoint/[endpointUUID].tsx @@ -8,14 +8,13 @@ import EndpointPage from "../../components/Endpoint"; const Endpoint = ({ endpoint }) => { return ( - + ); }; -// This gets called on every request export const getServerSideProps: GetServerSideProps = async (context) => { const endpoint = testEndpoints.find( (e) => e.uuid == context.query.endpointUUID diff --git a/frontend/src/testData.ts b/frontend/src/testData.ts index e0c75400..1f0c6756 100644 --- a/frontend/src/testData.ts +++ b/frontend/src/testData.ts @@ -1,6 +1,6 @@ -import { RiskScore } from "./types"; +import { Endpoint, RiskScore } from "./types"; -export const testEndpoints = [ +export const testEndpoints: Endpoint[] = [ { uuid: "5239bcfe-bf24-40e6-b952-b9811210108e", environment: "production", @@ -8,17 +8,38 @@ export const testEndpoints = [ path: "/foo/bar/{test}", method: "POST", riskScore: RiskScore.HIGH, - firstDetected: new Date().toISOString(), - lastActive: new Date().toISOString(), + firstDetected: "2022-07-31T00:52:10.586", + lastActive: "2022-07-31T00:52:10.586Z", + piiData: [ + { + dataType: "asdf", + dataPath: "result.asdf", + risk: RiskScore.HIGH, + dateIdentified: "2022-07-31T00:52:10.586Z", + }, + { + dataType: "foo", + dataPath: "result.asdf.asdf", + risk: RiskScore.MEDIUM, + dateIdentified: "2022-07-31T00:52:10.586Z", + }, + { + dataType: "bar", + dataPath: "result.asdf.bar", + risk: RiskScore.LOW, + dateIdentified: "2022-07-31T00:52:10.586Z", + }, + ], }, { - uuid: "5239bcfe-bf24-40e6-b952-b9811210108e", + uuid: "3425c51f-179b-45b6-9c1c-938f7f678f17", environment: "production", host: "AWS Gateway 1", - path: "/foo/bar/{test}", - method: "POST", + path: "/foo/blam/{test}", + method: "GET", riskScore: RiskScore.MEDIUM, - firstDetected: new Date().toISOString(), - lastActive: new Date().toISOString(), + firstDetected: "2022-07-31T00:52:10.586Z", + lastActive: "2022-07-31T00:52:10.586Z", + piiData: [], }, ]; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 392ec176..d21a3493 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1115,6 +1115,11 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chart.js@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.2.tgz#e3ebb88f7072780eec4183a788a990f4a58ba7a1" + integrity sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1484,6 +1489,11 @@ prop-types@^15.6.0, prop-types@^15.6.2: object-assign "^4.1.1" react-is "^16.13.1" +react-chartjs-2@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.3.1.tgz#9941e7397fb963f28bb557addb401e9ff96c6681" + integrity sha512-5i3mjP6tU7QSn0jvb8I4hudTzHJqS8l00ORJnVwI2sYu0ihpj83Lv2YzfxunfxTZkscKvZu2F2w9LkwNBhj6xA== + react-clientside-effect@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a"