Tidy up proxy logs, add copy action for headers

This commit is contained in:
David Stotijn
2020-09-27 18:59:38 +02:00
parent 854839daf8
commit ab90bfe4e9
11 changed files with 140 additions and 78 deletions

View File

@ -25,6 +25,13 @@ import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import ChevronRightIcon from "@material-ui/icons/ChevronRight"; import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import clsx from "clsx"; import clsx from "clsx";
export enum Page {
Home,
ProxySetup,
ProxyLogs,
Sender,
}
const drawerWidth = 240; const drawerWidth = 240;
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
@ -49,7 +56,7 @@ const useStyles = makeStyles((theme: Theme) =>
}), }),
}, },
menuButton: { menuButton: {
marginRight: 36, marginRight: 28,
}, },
hide: { hide: {
display: "none", display: "none",
@ -100,10 +107,20 @@ const useStyles = makeStyles((theme: Theme) =>
listItemIcon: { listItemIcon: {
minWidth: 42, minWidth: 42,
}, },
titleHighlight: {
color: theme.palette.secondary.main,
marginRight: 4,
},
}) })
); );
export function Layout(props: { children: React.ReactNode }): JSX.Element { interface Props {
children: React.ReactNode;
title: string;
page: Page;
}
export function Layout({ title, page, children }: Props): JSX.Element {
const classes = useStyles(); const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
@ -137,7 +154,10 @@ export function Layout(props: { children: React.ReactNode }): JSX.Element {
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
<Typography variant="h5" noWrap> <Typography variant="h5" noWrap>
Hetty:// <span className={title !== "" && classes.titleHighlight}>
Hetty://
</span>
{title}
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
@ -170,6 +190,7 @@ export function Layout(props: { children: React.ReactNode }): JSX.Element {
button button
component="a" component="a"
key="home" key="home"
selected={page === Page.Home}
className={classes.listItem} className={classes.listItem}
> >
<Tooltip title="Home"> <Tooltip title="Home">
@ -184,7 +205,8 @@ export function Layout(props: { children: React.ReactNode }): JSX.Element {
<ListItem <ListItem
button button
component="a" component="a"
key="proxy" key="proxyLogs"
selected={page === Page.ProxyLogs}
className={classes.listItem} className={classes.listItem}
> >
<Tooltip title="Proxy"> <Tooltip title="Proxy">
@ -200,6 +222,7 @@ export function Layout(props: { children: React.ReactNode }): JSX.Element {
button button
component="a" component="a"
key="sender" key="sender"
selected={page === Page.Sender}
className={classes.listItem} className={classes.listItem}
> >
<Tooltip title="Sender"> <Tooltip title="Sender">
@ -214,7 +237,7 @@ export function Layout(props: { children: React.ReactNode }): JSX.Element {
</Drawer> </Drawer>
<main className={classes.content}> <main className={classes.content}>
<div className={classes.toolbar} /> <div className={classes.toolbar} />
{props.children} {children}
</main> </main>
</div> </div>
); );

View File

@ -7,7 +7,10 @@ import {
TableCell, TableCell,
TableContainer, TableContainer,
TableRow, TableRow,
Snackbar,
} from "@material-ui/core"; } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import React, { useState } from "react";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
const paddingX = 0; const paddingX = 0;
@ -19,23 +22,35 @@ const useStyles = makeStyles((theme: Theme) => {
paddingBottom: paddingY, paddingBottom: paddingY,
verticalAlign: "top", verticalAlign: "top",
border: "none", border: "none",
whiteSpace: "nowrap" as any,
overflow: "hidden",
textOverflow: "ellipsis",
"&:hover": {
color: theme.palette.secondary.main,
whiteSpace: "inherit" as any,
overflow: "inherit",
textOverflow: "inherit",
cursor: "copy",
},
}; };
return createStyles({ return createStyles({
root: {},
table: { table: {
tableLayout: "fixed", tableLayout: "fixed",
width: "100%", width: "100%",
}, },
keyCell: { keyCell: {
...tableCell, ...tableCell,
paddingRight: theme.spacing(1),
width: "40%", width: "40%",
fontWeight: "bold", fontWeight: "bold",
fontSize: ".75rem",
}, },
valueCell: { valueCell: {
...tableCell, ...tableCell,
width: "60%", width: "60%",
border: "none", border: "none",
wordBreak: "break-all", fontSize: ".75rem",
whiteSpace: "pre-wrap",
}, },
}); });
}); });
@ -46,23 +61,59 @@ interface Props {
function HttpHeadersTable({ headers }: Props): JSX.Element { function HttpHeadersTable({ headers }: Props): JSX.Element {
const classes = useStyles(); const classes = useStyles();
const [open, setOpen] = useState(false);
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
const r = document.createRange();
r.selectNode(e.currentTarget);
window.getSelection().removeAllRanges();
window.getSelection().addRange(r);
document.execCommand("copy");
window.getSelection().removeAllRanges();
setOpen(true);
};
const handleClose = (event?: React.SyntheticEvent, reason?: string) => {
if (reason === "clickaway") {
return;
}
setOpen(false);
};
return ( return (
<TableContainer> <div>
<Table className={classes.table} size="small"> <Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
<TableBody> <Alert onClose={handleClose} severity="info">
{headers.map(({ key, value }, index) => ( Copied to clipboard.
<TableRow key={index}> </Alert>
<TableCell component="th" scope="row" className={classes.keyCell}> </Snackbar>
<code>{key}:</code> <TableContainer className={classes.root}>
</TableCell> <Table className={classes.table} size="small">
<TableCell className={classes.valueCell}> <TableBody>
<code>{value}</code> {headers.map(({ key, value }, index) => (
</TableCell> <TableRow key={index}>
</TableRow> <TableCell
))} component="th"
</TableBody> scope="row"
</Table> className={classes.keyCell}
</TableContainer> onClick={handleClick}
>
<code>{key}:</code>
</TableCell>
<TableCell className={classes.valueCell} onClick={handleClick}>
<code>{value}</code>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
); );
} }

View File

@ -1,19 +1,31 @@
import { green, orange, red } from "@material-ui/core/colors"; import { Theme, withTheme } from "@material-ui/core";
import { orange, red } from "@material-ui/core/colors";
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord"; import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
function HttpStatusIcon({ status }: { status: number }): JSX.Element { interface Props {
status: number;
theme: Theme;
}
function HttpStatusIcon({ status, theme }: Props): JSX.Element {
const style = { marginTop: "-.25rem", verticalAlign: "middle" }; const style = { marginTop: "-.25rem", verticalAlign: "middle" };
switch (Math.floor(status / 100)) { switch (Math.floor(status / 100)) {
case 2: case 2:
case 3: case 3:
return <FiberManualRecordIcon style={{ ...style, color: green[400] }} />; return (
<FiberManualRecordIcon
style={{ ...style, color: theme.palette.secondary.main }}
/>
);
case 4: case 4:
return <FiberManualRecordIcon style={{ ...style, color: orange[400] }} />; return (
<FiberManualRecordIcon style={{ ...style, color: orange["A400"] }} />
);
case 5: case 5:
return <FiberManualRecordIcon style={{ ...style, color: red[400] }} />; return <FiberManualRecordIcon style={{ ...style, color: red["A400"] }} />;
default: default:
return <FiberManualRecordIcon style={style} />; return <FiberManualRecordIcon style={style} />;
} }
} }
export default HttpStatusIcon; export default withTheme(HttpStatusIcon);

View File

@ -65,13 +65,13 @@ function LogDetail({ requestId: id }: Props): JSX.Element {
<div> <div>
<Grid container item spacing={2}> <Grid container item spacing={2}>
<Grid item xs={6}> <Grid item xs={6}>
<Box component={Paper} maxHeight="62vh" overflow="scroll"> <Box component={Paper}>
<RequestDetail request={{ method, url, proto, headers, body }} /> <RequestDetail request={{ method, url, proto, headers, body }} />
</Box> </Box>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
{response && ( {response && (
<Box component={Paper} maxHeight="62vh" overflow="scroll"> <Box component={Paper}>
<ResponseDetail response={response} /> <ResponseDetail response={response} />
</Box> </Box>
)} )}

View File

@ -57,7 +57,7 @@ function RequestDetail({ request }: Props): JSX.Element {
return ( return (
<div> <div>
<Box mx={2} my={2}> <Box p={2}>
<Typography <Typography
variant="overline" variant="overline"
color="textSecondary" color="textSecondary"
@ -79,7 +79,7 @@ function RequestDetail({ request }: Props): JSX.Element {
<Divider /> <Divider />
<Box m={2}> <Box p={2}>
<HttpHeadersTable headers={headers} /> <HttpHeadersTable headers={headers} />
</Box> </Box>

View File

@ -19,27 +19,13 @@ import CenteredPaper from "../CenteredPaper";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
requestTitle: { row: {
width: "calc(100% - 80px)", "&:hover": {
fontSize: "1rem", cursor: "pointer",
wordBreak: "break-all", },
whiteSpace: "pre-wrap",
},
headersTable: {
tableLayout: "fixed",
width: "100%",
},
headerKeyCell: {
verticalAlign: "top",
width: "30%",
fontWeight: "bold",
},
headerValueCell: {
width: "70%",
verticalAlign: "top",
wordBreak: "break-all",
whiteSpace: "pre-wrap",
}, },
/* Pseudo-class applied to the root element if `hover={true}`. */
hover: {},
}) })
); );
@ -88,6 +74,7 @@ function RequestListTable({
onLogClick, onLogClick,
theme, theme,
}: RequestListTableProps): JSX.Element { }: RequestListTableProps): JSX.Element {
const classes = useStyles();
return ( return (
<TableContainer <TableContainer
component={Paper} component={Paper}
@ -117,15 +104,15 @@ function RequestListTable({
const rowStyle = { const rowStyle = {
backgroundColor: backgroundColor:
id === selectedReqLogId id === selectedReqLogId && theme.palette.action.selected,
? theme.palette.action.selected
: "inherit",
}; };
return ( return (
<TableRow <TableRow
key={id} key={id}
className={classes.row}
style={rowStyle} style={rowStyle}
hover
onClick={() => onLogClick(id)} onClick={() => onLogClick(id)}
> >
<TableCell style={{ ...cellStyle, width: "100px" }}> <TableCell style={{ ...cellStyle, width: "100px" }}>

View File

@ -20,7 +20,7 @@ function ResponseDetail({ response }: Props): JSX.Element {
)?.value; )?.value;
return ( return (
<div> <div>
<Box mx={2} my={2}> <Box p={2}>
<Typography <Typography
variant="overline" variant="overline"
color="textSecondary" color="textSecondary"
@ -48,7 +48,7 @@ function ResponseDetail({ response }: Props): JSX.Element {
<Divider /> <Divider />
<Box m={2}> <Box p={2}>
<HttpHeadersTable headers={response.headers} /> <HttpHeadersTable headers={response.headers} />
</Box> </Box>

View File

@ -11,7 +11,7 @@ import SettingsEthernetIcon from "@material-ui/icons/SettingsEthernet";
import SendIcon from "@material-ui/icons/Send"; import SendIcon from "@material-ui/icons/Send";
import Link from "next/link"; import Link from "next/link";
import Layout from "../components/Layout"; import Layout, { Page } from "../components/Layout";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -33,7 +33,7 @@ const useStyles = makeStyles((theme: Theme) =>
function Index(): JSX.Element { function Index(): JSX.Element {
const classes = useStyles(); const classes = useStyles();
return ( return (
<Layout> <Layout page={Page.Home} title="">
<Box p={4}> <Box p={4}>
<Box mb={4} width="60%"> <Box mb={4} width="60%">
<Typography variant="h2"> <Typography variant="h2">

View File

@ -1,16 +1,13 @@
import React from "react"; import React from "react";
import { Box, Button, Typography } from "@material-ui/core"; import { Button, Typography } from "@material-ui/core";
import ListIcon from "@material-ui/icons/List"; import ListIcon from "@material-ui/icons/List";
import Link from "next/link"; import Link from "next/link";
import Layout from "../../components/Layout"; import Layout, { Page } from "../../components/Layout";
function Index(): JSX.Element { function Index(): JSX.Element {
return ( return (
<Layout> <Layout page={Page.ProxySetup} title="Proxy setup">
<Box mb={2}>
<Typography variant="h5">Proxy setup</Typography>
</Box>
<Typography paragraph>Coming soon</Typography> <Typography paragraph>Coming soon</Typography>
<Link href="/proxy/logs" passHref> <Link href="/proxy/logs" passHref>
<Button <Button

View File

@ -1,14 +1,9 @@
import { Typography, Box } from "@material-ui/core";
import LogsOverview from "../../../components/reqlog/LogsOverview"; import LogsOverview from "../../../components/reqlog/LogsOverview";
import Layout from "../../../components/Layout"; import Layout, { Page } from "../../../components/Layout";
function ProxyLogs(): JSX.Element { function ProxyLogs(): JSX.Element {
return ( return (
<Layout> <Layout page={Page.ProxyLogs} title="Proxy logs">
<Box mb={2}>
<Typography variant="h5">Proxy logs</Typography>
</Box>
<LogsOverview /> <LogsOverview />
</Layout> </Layout>
); );

View File

@ -1,13 +1,10 @@
import { Box, Typography } from "@material-ui/core"; import { Box, Typography } from "@material-ui/core";
import Layout from "../../components/Layout"; import Layout, { Page } from "../../components/Layout";
function Index(): JSX.Element { function Index(): JSX.Element {
return ( return (
<Layout> <Layout page={Page.Sender} title="Sender">
<Box mb={2}>
<Typography variant="h5">Sender</Typography>
</Box>
<Typography paragraph>Coming soon</Typography> <Typography paragraph>Coming soon</Typography>
</Layout> </Layout>
); );