mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Add headers to log details, add Monaco Editor
This commit is contained in:
55
admin/src/components/reqlog/Editor.tsx
Normal file
55
admin/src/components/reqlog/Editor.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import dynamic from "next/dynamic";
|
||||
const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });
|
||||
|
||||
const monacoOptions = {
|
||||
readOnly: true,
|
||||
wordWrap: "on",
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
type language = "html" | "typescript" | "json";
|
||||
|
||||
function editorDidMount() {
|
||||
return (window.MonacoEnvironment.getWorkerUrl = (moduleId, label) => {
|
||||
if (label === "json") return "/_next/static/json.worker.js";
|
||||
if (label === "html") return "/_next/static/html.worker.js";
|
||||
if (label === "javascript") return "/_next/static/ts.worker.js";
|
||||
|
||||
return "/_next/static/editor.worker.js";
|
||||
});
|
||||
}
|
||||
|
||||
function languageForContentType(contentType: string): language {
|
||||
switch (contentType) {
|
||||
case "text/html":
|
||||
return "html";
|
||||
case "application/json":
|
||||
return "json";
|
||||
case "application/javascript":
|
||||
return "typescript";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
function Editor({ content, contentType }: Props): JSX.Element {
|
||||
return (
|
||||
<MonacoEditor
|
||||
height={"600px"}
|
||||
language={languageForContentType(contentType)}
|
||||
theme="vs-dark"
|
||||
editorDidMount={editorDidMount}
|
||||
options={monacoOptions as any}
|
||||
value={content}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Editor;
|
58
admin/src/components/reqlog/HttpHeadersTable.tsx
Normal file
58
admin/src/components/reqlog/HttpHeadersTable.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
makeStyles,
|
||||
Theme,
|
||||
createStyles,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
table: {
|
||||
tableLayout: "fixed",
|
||||
width: "100%",
|
||||
},
|
||||
keyCell: {
|
||||
verticalAlign: "top",
|
||||
width: "30%",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
valueCell: {
|
||||
width: "70%",
|
||||
verticalAlign: "top",
|
||||
wordBreak: "break-all",
|
||||
whiteSpace: "pre-wrap",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
interface Props {
|
||||
headers: Array<{ key: string; value: string }>;
|
||||
}
|
||||
|
||||
function HttpHeadersTable({ headers }: Props): JSX.Element {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table className={classes.table} size="small">
|
||||
<TableBody>
|
||||
{headers.map(({ key, value }, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell component="th" scope="row" className={classes.keyCell}>
|
||||
<code>{key}</code>
|
||||
</TableCell>
|
||||
<TableCell className={classes.valueCell}>
|
||||
<code>{value}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default HttpHeadersTable;
|
@ -12,9 +12,17 @@ const HTTP_REQUEST_LOG = gql`
|
||||
method
|
||||
url
|
||||
proto
|
||||
headers {
|
||||
key
|
||||
value
|
||||
}
|
||||
body
|
||||
response {
|
||||
proto
|
||||
headers {
|
||||
key
|
||||
value
|
||||
}
|
||||
status
|
||||
statusCode
|
||||
body
|
||||
@ -43,14 +51,14 @@ function LogDetail({ requestId: id }: Props): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const { method, url, proto, body, response } = data.httpRequestLog;
|
||||
const { method, url, proto, headers, body, response } = data.httpRequestLog;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Grid container item spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Box component={Paper} maxHeight="62vh" overflow="scroll">
|
||||
<RequestDetail request={{ method, url, proto, body }} />
|
||||
<RequestDetail request={{ method, url, proto, headers, body }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
|
@ -1,43 +1,83 @@
|
||||
import { Typography, Box } from "@material-ui/core";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import React from "react";
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
Divider,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import HttpHeadersTable from "./HttpHeadersTable";
|
||||
import Editor from "./Editor";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
requestTitle: {
|
||||
width: "calc(100% - 80px)",
|
||||
fontSize: "1rem",
|
||||
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",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
interface Props {
|
||||
request: {
|
||||
method: string;
|
||||
url: string;
|
||||
proto: string;
|
||||
headers: Array<{ key: string; value: string }>;
|
||||
body?: string;
|
||||
};
|
||||
}
|
||||
|
||||
function RequestDetail({ request }: Props): JSX.Element {
|
||||
const { method, url, proto, body } = request;
|
||||
const { method, url, proto, headers, body } = request;
|
||||
const classes = useStyles();
|
||||
|
||||
const contentType = headers.find((header) => header.key === "Content-Type")
|
||||
?.value;
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Box m={3}>
|
||||
<Box mx={2} my={2}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
style={{ fontSize: "1rem", whiteSpace: "nowrap" }}
|
||||
variant="overline"
|
||||
color="textSecondary"
|
||||
style={{ float: "right" }}
|
||||
>
|
||||
Request
|
||||
</Typography>
|
||||
<Typography className={classes.requestTitle} variant="h6">
|
||||
{method} {decodeURIComponent(parsedUrl.pathname + parsedUrl.search)}{" "}
|
||||
{proto}
|
||||
<Typography component="span" color="textSecondary">
|
||||
{proto}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
{body && (
|
||||
<SyntaxHighlighter
|
||||
language="markup"
|
||||
showLineNumbers={true}
|
||||
style={vscDarkPlus}
|
||||
>
|
||||
{body}
|
||||
</SyntaxHighlighter>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<HttpHeadersTable headers={headers} />
|
||||
|
||||
{body && <Editor content={body} contentType={contentType} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,50 +1,52 @@
|
||||
import { Typography, Box } from "@material-ui/core";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { Typography, Box, Divider } from "@material-ui/core";
|
||||
|
||||
import HttpStatusIcon from "./HttpStatusCode";
|
||||
import Editor from "./Editor";
|
||||
import HttpHeadersTable from "./HttpHeadersTable";
|
||||
|
||||
interface Props {
|
||||
response: {
|
||||
proto: string;
|
||||
statusCode: number;
|
||||
status: string;
|
||||
headers: Array<{ key: string; value: string }>;
|
||||
body?: string;
|
||||
};
|
||||
}
|
||||
|
||||
function ResponseDetail({ response }: Props): JSX.Element {
|
||||
const contentType = response.headers.find(
|
||||
(header) => header.key === "Content-Type"
|
||||
)?.value;
|
||||
return (
|
||||
<div>
|
||||
<Box m={3}>
|
||||
<Box mx={2} my={2}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
color="textSecondary"
|
||||
style={{ float: "right" }}
|
||||
>
|
||||
Response
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
style={{ fontSize: "1rem", whiteSpace: "nowrap" }}
|
||||
>
|
||||
<HttpStatusIcon status={response.statusCode} /> {response.proto}{" "}
|
||||
<HttpStatusIcon status={response.statusCode} />{" "}
|
||||
<Typography component="span" color="textSecondary">
|
||||
{response.proto}
|
||||
</Typography>{" "}
|
||||
{response.status}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
{response.body && (
|
||||
<SyntaxHighlighter
|
||||
language="markup"
|
||||
showLineNumbers={true}
|
||||
showInlineLineNumbers={true}
|
||||
style={vscDarkPlus}
|
||||
lineProps={{
|
||||
style: {
|
||||
display: "block",
|
||||
wordBreak: "break-all",
|
||||
whiteSpace: "pre-wrap",
|
||||
},
|
||||
}}
|
||||
wrapLines={true}
|
||||
>
|
||||
{response.body}
|
||||
</SyntaxHighlighter>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<HttpHeadersTable headers={response.headers} />
|
||||
|
||||
{response.body && (
|
||||
<Editor content={response.body} contentType={contentType} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user