diff --git a/src/assets/icon-collapse.svg b/src/assets/icon-collapse.svg new file mode 100644 index 0000000..4dff74e --- /dev/null +++ b/src/assets/icon-collapse.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-copy.svg b/src/assets/icon-copy.svg new file mode 100644 index 0000000..3cc2679 --- /dev/null +++ b/src/assets/icon-copy.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-download-log.svg b/src/assets/icon-download-log.svg new file mode 100644 index 0000000..f4d5de7 --- /dev/null +++ b/src/assets/icon-download-log.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-expand.svg b/src/assets/icon-expand.svg new file mode 100644 index 0000000..04b730d --- /dev/null +++ b/src/assets/icon-expand.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-notification-active.svg b/src/assets/icon-notification-active.svg new file mode 100644 index 0000000..b37b9e5 --- /dev/null +++ b/src/assets/icon-notification-active.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-notification.svg b/src/assets/icon-notification.svg new file mode 100644 index 0000000..7873231 --- /dev/null +++ b/src/assets/icon-notification.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/icon-ready.svg b/src/assets/icon-ready.svg new file mode 100644 index 0000000..0188eef --- /dev/null +++ b/src/assets/icon-ready.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon-syncing.svg b/src/assets/icon-syncing.svg new file mode 100644 index 0000000..ed516ac --- /dev/null +++ b/src/assets/icon-syncing.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/components/data-display/LabeledRow.tsx b/src/common/components/data-display/LabeledRow.tsx index d8fc9d2..8b8f2a9 100644 --- a/src/common/components/data-display/LabeledRow.tsx +++ b/src/common/components/data-display/LabeledRow.tsx @@ -1,24 +1,48 @@ -import { Divider, Grid, makeStyles } from "@material-ui/core"; -import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined"; +import { Grid, makeStyles } from "@material-ui/core"; import React, { ReactElement } from "react"; import { copyToClipboard } from "../../utils/appUtil"; -import IconButton from "../input/buttons/IconButton"; +import { copyIcon } from "../../../common/utils/svgIcons"; +import ButtonBase from "@material-ui/core/ButtonBase"; type LabeledRowProps = { label: string; value: string | number; - paddingSpacing?: number; showCopyIcon?: boolean; + padding?: string; reserveSpaceForCopyIcon?: boolean; + statusIcon?: string; }; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(() => ({ cell: (props: LabeledRowProps) => ({ - padding: theme.spacing(props.paddingSpacing ?? 2), - textAlign: "center", + padding: props.padding ? props.padding : "15px 40px", wordWrap: "break-word", whiteSpace: "pre-wrap", + fontSize: "16px", }), + textLabel: { + color: "#979797", + }, + textValue: { + color: "#f2f2f2", + }, + copyIcon: { + paddingRight: "5px", + }, + copyContainer: { + cursor: "pointer", + color: "#f15a24", + fontSize: "14px", + display: "flex", + justifyContent: "flex-end", + }, + statusContainer: { + display: "flex", + alignItems: "center", + }, + statusIcon: { + marginRight: "10px", + }, })); const LabeledRow = (props: LabeledRowProps): ReactElement => { @@ -26,22 +50,42 @@ const LabeledRow = (props: LabeledRowProps): ReactElement => { const classes = useStyles(props); return ( - - + + {label} - - - {value} + + + {props.statusIcon ? ( +
+ status + {value} +
+ ) : ( +
{value}
+ )}
{reserveSpaceForCopyIcon && ( {showCopyIcon && ( - } - tooltipTitle="Copy to clipboard" - onClick={() => copyToClipboard(value)} - /> +
+ copyToClipboard(value)}> + copy + Copy + +
)}
)} diff --git a/src/common/components/data-display/text/PageTitle.tsx b/src/common/components/data-display/text/PageTitle.tsx new file mode 100644 index 0000000..3ab9e67 --- /dev/null +++ b/src/common/components/data-display/text/PageTitle.tsx @@ -0,0 +1,47 @@ +import React, { ReactElement } from "react"; +import { Icon } from "@material-ui/core"; +import { makeStyles } from "@material-ui/core"; +import { notificationIcon } from "../../../utils/svgIcons"; + +type PageTitleProps = { + title: string; +}; + +const useStyles = makeStyles(() => ({ + headingContainer: { + display: "flex", + alignItems: "center", + }, + heading: { + fontSize: "30px", + fontWeight: 500, + }, + notificationIconContainer: { + marginLeft: "auto", + }, + notificationIcon: { + cursor: "pointer", + }, +})); + +const PageTitle = (props: PageTitleProps): ReactElement => { + const { title } = props; + const classes = useStyles(); + + return ( +
+

{title}

+ {false && ( + + notifications + + )} +
+ ); +}; + +export default PageTitle; diff --git a/src/common/utils/svgIcons.ts b/src/common/utils/svgIcons.ts index 49a0514..9f6aaeb 100644 --- a/src/common/utils/svgIcons.ts +++ b/src/common/utils/svgIcons.ts @@ -1,3 +1,21 @@ import openDexLogo from "../../assets/OpenDEXLogo.svg"; +import expandIcon from "../../assets/icon-expand.svg"; +import readyIcon from "../../assets/icon-ready.svg"; +import syncingIcon from "../../assets/icon-syncing.svg"; +import copyIcon from "../../assets/icon-copy.svg"; +import notificationIcon from "../../assets/icon-notification.svg"; +import notificationActiveIcon from "../../assets/icon-notification-active.svg"; +import downloadIcon from "../../assets/icon-download-log.svg"; +import collapseIcon from "../../assets/icon-collapse.svg"; -export { openDexLogo }; +export { + openDexLogo, + expandIcon, + readyIcon, + syncingIcon, + copyIcon, + notificationIcon, + notificationActiveIcon, + downloadIcon, + collapseIcon, +}; diff --git a/src/dashboard/Dashboard.tsx b/src/dashboard/Dashboard.tsx index d89aba1..d79e9f1 100644 --- a/src/dashboard/Dashboard.tsx +++ b/src/dashboard/Dashboard.tsx @@ -44,7 +44,7 @@ const useStyles = makeStyles((theme: Theme) => content: { marginLeft: drawerWidth, backgroundColor: theme.palette.background.default, - padding: theme.spacing(3), + padding: theme.spacing(4), height: "100vh", display: "flex", flexDirection: "column", diff --git a/src/dashboard/overview/Overview.tsx b/src/dashboard/overview/Overview.tsx index d28e2ca..d6d020c 100644 --- a/src/dashboard/overview/Overview.tsx +++ b/src/dashboard/overview/Overview.tsx @@ -11,6 +11,7 @@ import DashboardContent, { DashboardContentState, } from "../DashboardContent"; import OverviewItem from "./OverviewItem"; +import PageTitle from "../../common/components/data-display/text/PageTitle"; type PropsType = DashboardContentProps & WithStyles; @@ -23,6 +24,20 @@ const styles = () => { wrapper: { overflowY: "auto", }, + headingContainer: { + display: "flex", + alignItems: "center", + }, + heading: { + fontSize: "30px", + fontWeight: 500, + }, + notificationIconContainer: { + marginLeft: "auto", + }, + notificationIcon: { + cursor: "pointer", + }, }); }; @@ -58,32 +73,41 @@ class Overview extends DashboardContent { return status.status !== "Disabled"; }; + handleSelectNotifications = () => { + console.log("handleSelectNotifications"); + }; + render(): ReactElement { const { classes } = this.props; return ( - - {this.state.initialLoadCompleted ? ( - this.state.statuses && - this.state.statuses - .filter(this.statusFilter) - .sort( - (a, b) => - this.sortingOrderByService(a.service) - - this.sortingOrderByService(b.service) - ) - .map((status) => ( - - )) - ) : ( - - )} - +
+ + + {this.state.initialLoadCompleted ? ( + this.state.statuses && + this.state.statuses + .filter(this.statusFilter) + .sort( + (a, b) => + this.sortingOrderByService(a.service) - + this.sortingOrderByService(b.service) + ) + .map((status) => ( + + + + )) + ) : ( + + )} + +
); } } diff --git a/src/dashboard/overview/OverviewItem.tsx b/src/dashboard/overview/OverviewItem.tsx index 356cd96..c57a821 100644 --- a/src/dashboard/overview/OverviewItem.tsx +++ b/src/dashboard/overview/OverviewItem.tsx @@ -1,27 +1,24 @@ -import { - createStyles, - Divider, - Icon, - makeStyles, - Theme, -} from "@material-ui/core"; +import { Icon, makeStyles } from "@material-ui/core"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import Grid from "@material-ui/core/Grid"; import Typography from "@material-ui/core/Typography"; -import GetAppOutlinedIcon from "@material-ui/icons/GetAppOutlined"; import React, { ReactElement, useState } from "react"; -import { Subject } from "rxjs"; -import api from "../../api"; -import Snackbar from "../../common/components/data-display/feedback/Snackbar"; -import LabeledRow from "../../common/components/data-display/LabeledRow"; -import IconButton from "../../common/components/input/buttons/IconButton"; -import TextButton from "../../common/components/input/buttons/TextButton"; -import { formatDateTimeForFilename } from "../../common/utils/dateUtil"; import { isServiceReady } from "../../common/utils/serviceUtil"; import { SERVICES_WITH_ADDITIONAL_INFO } from "../../constants"; import { Status } from "../../models/Status"; import ServiceDetails from "./ServiceDetails"; +import { + expandIcon, + readyIcon, + syncingIcon, + downloadIcon, +} from "../../common/utils/svgIcons"; +import ButtonBase from "@material-ui/core/ButtonBase"; +import api from "../../api"; +import { formatDateTimeForFilename } from "../../common/utils/dateUtil"; +import { Subject } from "rxjs"; +import Snackbar from "../../common/components/data-display/feedback/Snackbar"; export type OverviewItemProps = { status: Status; @@ -29,58 +26,73 @@ export type OverviewItemProps = { opendexdNotReady?: boolean; }; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - cardHeader: { - padding: theme.spacing(3), - }, - cardContent: { - padding: 0, - "&:last-child": { - paddingBottom: 0, - }, - }, - statusDot: { - height: 10, - width: 10, - borderRadius: "50%", - display: "inline-block", - marginRight: 10, - }, - active: { - backgroundColor: theme.palette.success.light, - }, - inactive: { - backgroundColor: theme.palette.error.light, - }, - }) -); +const getCardHeaderBackgroundImage = (props: OverviewItemProps) => { + if (isServiceReady(props.status)) { + return "linear-gradient(to right, #1f995a, #00e64d)"; + } else { + return "linear-gradient(to left, #fea900, #f15a24 0%)"; + } +}; -const downloadLogs = (serviceName: string, handleError: () => void): void => { - api.logs$(serviceName).subscribe({ - next: (resp: string) => { - const blob = new Blob([resp]); - const url = URL.createObjectURL(blob); - const anchor = Object.assign(document.createElement("a"), { - href: url, - download: `${serviceName}_${formatDateTimeForFilename(new Date())}.log`, - style: { display: "none" }, - }); - anchor.click(); +const useStyles = makeStyles(() => ({ + cardHeader: (props: OverviewItemProps) => ({ + padding: "12px 16px 12px 24px", + backgroundImage: getCardHeaderBackgroundImage(props), + justifyContent: "space-between", + alignItems: "center", + }), + cardHeading: { + fontSize: "20px", + color: "#000000", + }, + cardContainer: { + padding: 0, + "&:last-child": { + paddingBottom: 0, }, - error: handleError, - }); -}; + }, + cardContent: { + fontSize: "16px", + display: "flex", + padding: "35px 25px", + }, + cardContentStatusText: { + color: "#979797", + }, + cardContentStatusValueText: { + color: "#f2f2f2", + }, + expandIcon: { + cursor: "pointer", + }, + expandIconContainer: { + verticalAlign: "middle", + }, + statusIcon: { + padding: "0 10px", + }, + downloadIconContainer: { + fontSize: "14px", + color: "#000000", + textDecoration: "underline", + }, + detailsIconContainer: { + display: "flex", + justifyContent: "flex-end", + }, + titleAndLogsContainer: { + width: "66.6%", + display: "flex", + justifyContent: "space-between", + alignItems: "center", + }, +})); const OverviewItem = (props: OverviewItemProps): ReactElement => { const { status, opendexdLocked, opendexdNotReady } = props; const [detailsOpen, setDetailsOpen] = useState(false); + const classes = useStyles(props); const errorMsgOpenSubject = new Subject(); - const classes = useStyles(); - - const statusDotClass = `${classes.statusDot} ${ - isServiceReady(status) ? classes.active : classes.inactive - }`; const isDetailsIconVisible = (status: Status): boolean => { return ( @@ -91,73 +103,108 @@ const OverviewItem = (props: OverviewItemProps): ReactElement => { ); }; + const getStatusIcon = (status: Status) => { + if (isServiceReady(status)) { + return readyIcon; + } else { + return syncingIcon; + } + }; + const isDownloadLogsEnabled = (status: Status): boolean => { return ( !status.status.includes("light mode") && status.status !== "Disabled" ); }; + const downloadLogs = (serviceName: string, handleError: () => void): void => { + api.logs$(serviceName).subscribe({ + next: (resp: string) => { + const blob = new Blob([resp]); + const url = URL.createObjectURL(blob); + const anchor = Object.assign(document.createElement("a"), { + href: url, + download: `${serviceName}_${formatDateTimeForFilename( + new Date() + )}.log`, + style: { display: "none" }, + }); + anchor.click(); + }, + error: handleError, + }); + }; + return ( - - - - +
+ + +
+ + + {props.status.service} + + + + + {isDownloadLogsEnabled(status) && ( +
+ + downloadLogs(status.service, () => + errorMsgOpenSubject?.next(true) + ) + } + > + download logs + Download Logs + +
+ )} +
+
+ + {isDetailsIconVisible(status) && ( - setDetailsOpen(true)} - icon={open_in_full} - tooltipTitle="Details" - /> - )} - - - - - {props.status.service} - - - - {isDownloadLogsEnabled(status) && ( - - downloadLogs(status.service, () => - errorMsgOpenSubject?.next(true) - ) - } - size="small" - startIcon={} - tooltipTitle="Download logs" - /> + className={classes.expandIconContainer} + > + expand + )}
- - - + + +
+

Status:

+ status +

+ {props.status.status} +

+
- {detailsOpen && ( setDetailsOpen(false)} + titleBackground={getCardHeaderBackgroundImage(props)} /> )} @@ -166,7 +213,7 @@ const OverviewItem = (props: OverviewItemProps): ReactElement => { openSubject={errorMsgOpenSubject} type="error" > - +
); }; diff --git a/src/dashboard/overview/ServiceDetails.tsx b/src/dashboard/overview/ServiceDetails.tsx index 5ec1075..6fd34bb 100644 --- a/src/dashboard/overview/ServiceDetails.tsx +++ b/src/dashboard/overview/ServiceDetails.tsx @@ -1,49 +1,90 @@ -import { createStyles, makeStyles, Theme } from "@material-ui/core"; +import { makeStyles, Theme } from "@material-ui/core"; import React, { ReactElement } from "react"; -import Dialog from "../../common/components/layout/dialog/Dialog"; import { drawerWidth } from "../../common/components/navigation/Menu"; import { Status } from "../../models/Status"; import ServiceDetailsContent from "./ServiceDetailsContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import Dialog from "@material-ui/core/Dialog"; +import DialogContent from "@material-ui/core/DialogContent"; +import { collapseIcon } from "../../common/utils/svgIcons"; +import ButtonBase from "@material-ui/core/ButtonBase"; export type ServiceDetailsProps = { status: Status; + statusIcon: string; handleClose: () => void; + titleBackground: string; }; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - dialog: { - position: "absolute", - left: drawerWidth, - top: 0, - margin: theme.spacing(3), - width: "100%", - maxWidth: `calc(100% - ${drawerWidth + theme.spacing(3) * 2}px)`, - backgroundColor: theme.palette.background.default, - }, - }) -); +const useStyles = makeStyles((theme: Theme) => ({ + dialog: { + position: "absolute", + left: drawerWidth, + top: 80, + margin: theme.spacing(3), + width: "100%", + maxWidth: `calc(100% - ${drawerWidth + theme.spacing(3) * 2}px)`, + backgroundColor: theme.palette.background.default, + borderRadius: 0, + }, + dialogTitle: { + fontSize: "18px", + color: "#000000", + }, + dialogTitleContainer: (props: ServiceDetailsProps) => ({ + backgroundImage: props.titleBackground, + padding: "8px", + }), + dialogTitleContent: { + display: "flex", + alignItems: "center", + padding: "5px 15px", + }, + logsAndCloseContainer: { + display: "flex", + marginLeft: "auto", + alignItems: "center", + }, + dialogContent: { + padding: 0, + }, +})); const ServiceDetails = (props: ServiceDetailsProps): ReactElement => { - const { status, handleClose } = props; - const classes = useStyles(); - - const title = `General ${status.service} info`; + const { status, handleClose, statusIcon } = props; + const classes = useStyles(props); return ( - - } - /> +
+ + +
+ {`General ${status.service} info`} + +
+ + collapse + +
+
+
+ + + +
+
); }; diff --git a/src/dashboard/overview/ServiceDetailsContent.tsx b/src/dashboard/overview/ServiceDetailsContent.tsx index 61197f5..54216eb 100644 --- a/src/dashboard/overview/ServiceDetailsContent.tsx +++ b/src/dashboard/overview/ServiceDetailsContent.tsx @@ -18,6 +18,7 @@ import LabeledRow from "../../common/components/data-display/LabeledRow"; export type ServiceDetailsContentProps = { status: Status; closeDetails: () => void; + statusIcon: string; }; type InfoRow = { @@ -29,7 +30,8 @@ type InfoRow = { const useStyles = makeStyles((theme: Theme) => createStyles({ content: { - padding: 0, + padding: "20px", + backgroundColor: "#161616", }, textRow: { padding: theme.spacing(3), @@ -125,7 +127,7 @@ const createLndRows = (lndInfo: LndInfo): InfoRow[] => [ const ServiceDetailsContent = ( props: ServiceDetailsContentProps ): ReactElement => { - const { status, closeDetails } = props; + const { status, closeDetails, statusIcon } = props; const classes = useStyles(); const [rows, setRows] = useState([]); const [initialLoadComplete, setInitialLoadComplete] = useState(false); @@ -151,6 +153,7 @@ const ServiceDetailsContent = ( label={row.label} value={row.value} showCopyIcon={row.copyIcon} + statusIcon={row.label === "Status" ? statusIcon : ""} reserveSpaceForCopyIcon /> )) diff --git a/src/stories/OverviewItem.stories.tsx b/src/stories/OverviewItem.stories.tsx new file mode 100644 index 0000000..63d84d9 --- /dev/null +++ b/src/stories/OverviewItem.stories.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { storiesOf } from "@storybook/react"; +import OverviewItem from "../dashboard/overview/OverviewItem"; + +export type Status = { + service: string; + status: string; +}; + +storiesOf("Overview", module).add("OverviewItem", () => ( + +)); diff --git a/src/themes.ts b/src/themes.ts index fbd7c13..020dab7 100644 --- a/src/themes.ts +++ b/src/themes.ts @@ -13,6 +13,9 @@ export const darkTheme = createMuiTheme({ paper: "#151515", }, }, + typography: { + fontFamily: ["ApercuPro", "Arial", "sans-serif"].join(","), + }, }); export const tradeTheme = createMuiTheme({