mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Update Next.js, Material UI
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
|
||||
export function useConfirmationDialog() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -38,12 +38,10 @@ export function ConfirmationDialog(props: ConfirmationDialog) {
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{children}
|
||||
</DialogContentText>
|
||||
<DialogContentText id="alert-dialog-description">{children}</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Abort</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={confirm} autoFocus>
|
||||
Confirm
|
||||
</Button>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dynamic from "next/dynamic";
|
||||
const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
import monaco from "monaco-editor/esm/vs/editor/editor.api";
|
||||
|
||||
const monacoOptions = {
|
||||
const monacoOptions: monaco.editor.IEditorOptions = {
|
||||
readOnly: true,
|
||||
wordWrap: "on",
|
||||
minimap: {
|
||||
@ -11,20 +11,7 @@ const monacoOptions = {
|
||||
|
||||
type language = "html" | "typescript" | "json";
|
||||
|
||||
function editorDidMount() {
|
||||
return ((window as any).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 {
|
||||
function languageForContentType(contentType?: string): language | undefined {
|
||||
switch (contentType) {
|
||||
case "text/html":
|
||||
return "html";
|
||||
@ -41,7 +28,7 @@ function languageForContentType(contentType: string): language {
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
contentType: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
function Editor({ content, contentType }: Props): JSX.Element {
|
||||
@ -50,8 +37,7 @@ function Editor({ content, contentType }: Props): JSX.Element {
|
||||
height={"600px"}
|
||||
language={languageForContentType(contentType)}
|
||||
theme="vs-dark"
|
||||
editorDidMount={editorDidMount}
|
||||
options={monacoOptions as any}
|
||||
options={monacoOptions}
|
||||
value={content}
|
||||
/>
|
||||
);
|
||||
|
@ -1,83 +1,66 @@
|
||||
import {
|
||||
makeStyles,
|
||||
Theme,
|
||||
createStyles,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Snackbar,
|
||||
} from "@material-ui/core";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Table, TableBody, TableCell, TableContainer, TableRow, Snackbar } from "@mui/material";
|
||||
import { Alert } from "@mui/lab";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
const paddingX = 0;
|
||||
const paddingY = theme.spacing(1) / 3;
|
||||
const tableCell = {
|
||||
paddingLeft: paddingX,
|
||||
paddingRight: paddingX,
|
||||
paddingTop: paddingY,
|
||||
paddingBottom: paddingY,
|
||||
verticalAlign: "top",
|
||||
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({
|
||||
root: {},
|
||||
table: {
|
||||
tableLayout: "fixed",
|
||||
width: "100%",
|
||||
},
|
||||
keyCell: {
|
||||
...tableCell,
|
||||
paddingRight: theme.spacing(1),
|
||||
width: "40%",
|
||||
fontWeight: "bold",
|
||||
fontSize: ".75rem",
|
||||
},
|
||||
valueCell: {
|
||||
...tableCell,
|
||||
width: "60%",
|
||||
border: "none",
|
||||
fontSize: ".75rem",
|
||||
},
|
||||
});
|
||||
});
|
||||
const baseCellStyle = {
|
||||
px: 0,
|
||||
py: 0.33,
|
||||
verticalAlign: "top",
|
||||
border: "none",
|
||||
whiteSpace: "nowrap" as any,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
"&:hover": {
|
||||
color: "primary.main",
|
||||
whiteSpace: "inherit" as any,
|
||||
overflow: "inherit",
|
||||
textOverflow: "inherit",
|
||||
cursor: "copy",
|
||||
},
|
||||
};
|
||||
|
||||
const keyCellStyle = {
|
||||
...baseCellStyle,
|
||||
pr: 1,
|
||||
width: "40%",
|
||||
fontWeight: "bold",
|
||||
fontSize: ".75rem",
|
||||
};
|
||||
|
||||
const valueCellStyle = {
|
||||
...baseCellStyle,
|
||||
width: "60%",
|
||||
border: "none",
|
||||
fontSize: ".75rem",
|
||||
};
|
||||
|
||||
interface Props {
|
||||
headers: Array<{ key: string; value: string }>;
|
||||
}
|
||||
|
||||
function HttpHeadersTable({ headers }: Props): JSX.Element {
|
||||
const classes = useStyles();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const windowSel = window.getSelection();
|
||||
|
||||
if (!windowSel || !document) {
|
||||
return;
|
||||
}
|
||||
|
||||
const r = document.createRange();
|
||||
r.selectNode(e.currentTarget);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(r);
|
||||
windowSel.removeAllRanges();
|
||||
windowSel.addRange(r);
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeAllRanges();
|
||||
windowSel.removeAllRanges();
|
||||
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = (event?: React.SyntheticEvent, reason?: string) => {
|
||||
const handleClose = (event: Event | React.SyntheticEvent, reason?: string) => {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
@ -92,20 +75,21 @@ function HttpHeadersTable({ headers }: Props): JSX.Element {
|
||||
Copied to clipboard.
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<TableContainer className={classes.root}>
|
||||
<Table className={classes.table} size="small">
|
||||
<TableContainer>
|
||||
<Table
|
||||
sx={{
|
||||
tableLayout: "fixed",
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<TableBody>
|
||||
{headers.map(({ key, value }, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.keyCell}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<TableCell component="th" scope="row" sx={keyCellStyle} onClick={handleClick}>
|
||||
<code>{key}:</code>
|
||||
</TableCell>
|
||||
<TableCell className={classes.valueCell} onClick={handleClick}>
|
||||
<TableCell sx={valueCellStyle} onClick={handleClick}>
|
||||
<code>{value}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
@ -1,31 +1,25 @@
|
||||
import { Theme, withTheme } from "@material-ui/core";
|
||||
import { orange, red } from "@material-ui/core/colors";
|
||||
import FiberManualRecordIcon from "@material-ui/icons/FiberManualRecord";
|
||||
import { SvgIconTypeMap } from "@mui/material";
|
||||
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
|
||||
|
||||
interface Props {
|
||||
status: number;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
function HttpStatusIcon({ status, theme }: Props): JSX.Element {
|
||||
const style = { marginTop: "-.25rem", verticalAlign: "middle" };
|
||||
export default function HttpStatusIcon({ status }: Props): JSX.Element {
|
||||
let color: SvgIconTypeMap["props"]["color"] = "inherit";
|
||||
|
||||
switch (Math.floor(status / 100)) {
|
||||
case 2:
|
||||
case 3:
|
||||
return (
|
||||
<FiberManualRecordIcon
|
||||
style={{ ...style, color: theme.palette.secondary.main }}
|
||||
/>
|
||||
);
|
||||
color = "primary";
|
||||
break;
|
||||
case 4:
|
||||
return (
|
||||
<FiberManualRecordIcon style={{ ...style, color: orange["A400"] }} />
|
||||
);
|
||||
color = "warning";
|
||||
break;
|
||||
case 5:
|
||||
return <FiberManualRecordIcon style={{ ...style, color: red["A400"] }} />;
|
||||
default:
|
||||
return <FiberManualRecordIcon style={style} />;
|
||||
color = "error";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(HttpStatusIcon);
|
||||
return <FiberManualRecordIcon sx={{ marginTop: "-.25rem", verticalAlign: "middle" }} color={color} />;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
import { Box, Grid, Paper, CircularProgress } from "@material-ui/core";
|
||||
import { Box, Grid, Paper, CircularProgress } from "@mui/material";
|
||||
|
||||
import ResponseDetail from "./ResponseDetail";
|
||||
import RequestDetail from "./RequestDetail";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import Alert from "@mui/lab/Alert";
|
||||
|
||||
const HTTP_REQUEST_LOG = gql`
|
||||
query HttpRequestLog($id: ID!) {
|
||||
@ -44,11 +44,7 @@ function LogDetail({ requestId: id }: Props): JSX.Element {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Error fetching logs details: {error.message}
|
||||
</Alert>
|
||||
);
|
||||
return <Alert severity="error">Error fetching logs details: {error.message}</Alert>;
|
||||
}
|
||||
|
||||
if (!data.httpRequestLog) {
|
||||
|
@ -1,12 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Link as MaterialLink,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import { Box, CircularProgress, Link as MaterialLink, Typography } from "@mui/material";
|
||||
import Alert from "@mui/lab/Alert";
|
||||
|
||||
import RequestList from "./RequestList";
|
||||
import LogDetail from "./LogDetail";
|
||||
@ -33,7 +28,7 @@ function LogsOverview(): JSX.Element {
|
||||
<Alert severity="info">
|
||||
There is no project active.{" "}
|
||||
<Link href="/projects" passHref>
|
||||
<MaterialLink color="secondary">Create or open</MaterialLink>
|
||||
<MaterialLink color="primary">Create or open</MaterialLink>
|
||||
</Link>{" "}
|
||||
one first.
|
||||
</Alert>
|
||||
@ -47,11 +42,7 @@ function LogsOverview(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Box mb={2}>
|
||||
<RequestList
|
||||
logs={logs}
|
||||
selectedReqLogId={detailReqLogId}
|
||||
onLogClick={handleLogClick}
|
||||
/>
|
||||
<RequestList logs={logs || []} selectedReqLogId={detailReqLogId} onLogClick={handleLogClick} />
|
||||
</Box>
|
||||
<Box>
|
||||
{detailReqLogId && <LogDetail requestId={detailReqLogId} />}
|
||||
|
@ -1,42 +1,9 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
Divider,
|
||||
} from "@material-ui/core";
|
||||
import { Typography, Box, Divider } from "@mui/material";
|
||||
|
||||
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;
|
||||
@ -49,29 +16,27 @@ interface Props {
|
||||
|
||||
function RequestDetail({ request }: Props): JSX.Element {
|
||||
const { method, url, proto, headers, body } = request;
|
||||
const classes = useStyles();
|
||||
|
||||
const contentType = headers.find((header) => header.key === "Content-Type")
|
||||
?.value;
|
||||
const contentType = headers.find((header) => header.key === "Content-Type")?.value;
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Box p={2}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
color="textSecondary"
|
||||
style={{ float: "right" }}
|
||||
>
|
||||
<Typography variant="overline" color="textSecondary" style={{ float: "right" }}>
|
||||
Request
|
||||
</Typography>
|
||||
<Typography className={classes.requestTitle} variant="h6">
|
||||
<Typography
|
||||
sx={{
|
||||
width: "calc(100% - 80px)",
|
||||
fontSize: "1rem",
|
||||
wordBreak: "break-all",
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
variant="h6"
|
||||
>
|
||||
{method} {decodeURIComponent(parsedUrl.pathname + parsedUrl.search)}{" "}
|
||||
<Typography
|
||||
component="span"
|
||||
color="textSecondary"
|
||||
style={{ fontFamily: "'JetBrains Mono', monospace" }}
|
||||
>
|
||||
<Typography component="span" color="textSecondary" style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
{proto}
|
||||
</Typography>
|
||||
</Typography>
|
||||
|
@ -8,48 +8,23 @@ import {
|
||||
TableBody,
|
||||
Typography,
|
||||
Box,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withTheme,
|
||||
} from "@material-ui/core";
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import HttpStatusIcon from "./HttpStatusCode";
|
||||
import CenteredPaper from "../CenteredPaper";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
row: {
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
/* Pseudo-class applied to the root element if `hover={true}`. */
|
||||
hover: {},
|
||||
})
|
||||
);
|
||||
import { RequestLog } from "../../lib/requestLogs";
|
||||
|
||||
interface Props {
|
||||
logs: Array<any>;
|
||||
logs: RequestLog[];
|
||||
selectedReqLogId?: string;
|
||||
onLogClick(requestId: string): void;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
function RequestList({
|
||||
logs,
|
||||
onLogClick,
|
||||
selectedReqLogId,
|
||||
theme,
|
||||
}: Props): JSX.Element {
|
||||
export default function RequestList({ logs, onLogClick, selectedReqLogId }: Props): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<RequestListTable
|
||||
onLogClick={onLogClick}
|
||||
logs={logs}
|
||||
selectedReqLogId={selectedReqLogId}
|
||||
theme={theme}
|
||||
/>
|
||||
<RequestListTable onLogClick={onLogClick} logs={logs} selectedReqLogId={selectedReqLogId} />
|
||||
{logs.length === 0 && (
|
||||
<Box my={1}>
|
||||
<CenteredPaper>
|
||||
@ -62,19 +37,14 @@ function RequestList({
|
||||
}
|
||||
|
||||
interface RequestListTableProps {
|
||||
logs?: any;
|
||||
logs: RequestLog[];
|
||||
selectedReqLogId?: string;
|
||||
onLogClick(requestId: string): void;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
function RequestListTable({
|
||||
logs,
|
||||
selectedReqLogId,
|
||||
onLogClick,
|
||||
theme,
|
||||
}: RequestListTableProps): JSX.Element {
|
||||
const classes = useStyles();
|
||||
function RequestListTable({ logs, selectedReqLogId, onLogClick }: RequestListTableProps): JSX.Element {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
@ -102,26 +72,25 @@ function RequestListTable({
|
||||
textOverflow: "ellipsis",
|
||||
} as any;
|
||||
|
||||
const rowStyle = {
|
||||
backgroundColor:
|
||||
id === selectedReqLogId && theme.palette.action.selected,
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={id}
|
||||
className={classes.row}
|
||||
style={rowStyle}
|
||||
sx={{
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
...(id === selectedReqLogId && {
|
||||
bgcolor: theme.palette.action.selected,
|
||||
}),
|
||||
}}
|
||||
hover
|
||||
onClick={() => onLogClick(id)}
|
||||
>
|
||||
<TableCell style={{ ...cellStyle, width: "100px" }}>
|
||||
<code>{method}</code>
|
||||
</TableCell>
|
||||
<TableCell style={{ ...cellStyle, maxWidth: "100px" }}>
|
||||
{origin}
|
||||
</TableCell>
|
||||
<TableCell style={{ ...cellStyle, maxWidth: "200px" }}>
|
||||
<TableCell sx={{ ...cellStyle, maxWidth: "100px" }}>{origin}</TableCell>
|
||||
<TableCell sx={{ ...cellStyle, maxWidth: "200px" }}>
|
||||
{decodeURIComponent(pathname + search + hash)}
|
||||
</TableCell>
|
||||
<TableCell style={{ maxWidth: "100px" }}>
|
||||
@ -142,5 +111,3 @@ function RequestListTable({
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(RequestList);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Typography, Box, Divider } from "@material-ui/core";
|
||||
import { Typography, Box, Divider } from "@mui/material";
|
||||
|
||||
import HttpStatusIcon from "./HttpStatusCode";
|
||||
import Editor from "./Editor";
|
||||
@ -15,30 +15,17 @@ interface Props {
|
||||
}
|
||||
|
||||
function ResponseDetail({ response }: Props): JSX.Element {
|
||||
const contentType = response.headers.find(
|
||||
(header) => header.key === "Content-Type"
|
||||
)?.value;
|
||||
const contentType = response.headers.find((header) => header.key === "Content-Type")?.value;
|
||||
return (
|
||||
<div>
|
||||
<Box p={2}>
|
||||
<Typography
|
||||
variant="overline"
|
||||
color="textSecondary"
|
||||
style={{ float: "right" }}
|
||||
>
|
||||
<Typography variant="overline" color="textSecondary" style={{ float: "right" }}>
|
||||
Response
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
style={{ fontSize: "1rem", whiteSpace: "nowrap" }}
|
||||
>
|
||||
<Typography variant="h6" style={{ fontSize: "1rem", whiteSpace: "nowrap" }}>
|
||||
<HttpStatusIcon status={response.statusCode} />{" "}
|
||||
<Typography component="span" color="textSecondary">
|
||||
<Typography
|
||||
component="span"
|
||||
color="textSecondary"
|
||||
style={{ fontFamily: "'JetBrains Mono', monospace" }}
|
||||
>
|
||||
<Typography component="span" color="textSecondary" style={{ fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
{response.proto}
|
||||
</Typography>
|
||||
</Typography>{" "}
|
||||
@ -52,9 +39,7 @@ function ResponseDetail({ response }: Props): JSX.Element {
|
||||
<HttpHeadersTable headers={response.headers} />
|
||||
</Box>
|
||||
|
||||
{response.body && (
|
||||
<Editor content={response.body} contentType={contentType} />
|
||||
)}
|
||||
{response.body && <Editor content={response.body} contentType={contentType} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,29 +3,23 @@ import {
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
ClickAwayListener,
|
||||
createStyles,
|
||||
FormControlLabel,
|
||||
InputBase,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Popper,
|
||||
Theme,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from "@material-ui/core";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import FilterListIcon from "@material-ui/icons/FilterList";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
} from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||
import { withoutTypename } from "../../lib/omitTypename";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Alert } from "@mui/lab";
|
||||
import { useClearHTTPRequestLog } from "./hooks/useClearHTTPRequestLog";
|
||||
import {
|
||||
ConfirmationDialog,
|
||||
useConfirmationDialog,
|
||||
} from "./ConfirmationDialog";
|
||||
import { ConfirmationDialog, useConfirmationDialog } from "./ConfirmationDialog";
|
||||
|
||||
const FILTER = gql`
|
||||
query HttpRequestLogFilter {
|
||||
@ -45,79 +39,43 @@ const SET_FILTER = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
padding: "2px 4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: 400,
|
||||
},
|
||||
input: {
|
||||
marginLeft: theme.spacing(1),
|
||||
flex: 1,
|
||||
},
|
||||
iconButton: {
|
||||
padding: 10,
|
||||
},
|
||||
filterPopper: {
|
||||
width: 400,
|
||||
marginTop: 6,
|
||||
zIndex: 99,
|
||||
},
|
||||
filterOptions: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
filterLoading: {
|
||||
marginRight: 1,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export interface SearchFilter {
|
||||
onlyInScope: boolean;
|
||||
searchExpression: string;
|
||||
}
|
||||
|
||||
function Search(): JSX.Element {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const [searchExpr, setSearchExpr] = useState("");
|
||||
const { loading: filterLoading, error: filterErr, data: filter } = useQuery(
|
||||
FILTER,
|
||||
{
|
||||
onCompleted: (data) => {
|
||||
setSearchExpr(data.httpRequestLogFilter?.searchExpression || "");
|
||||
},
|
||||
}
|
||||
);
|
||||
const {
|
||||
loading: filterLoading,
|
||||
error: filterErr,
|
||||
data: filter,
|
||||
} = useQuery(FILTER, {
|
||||
onCompleted: (data) => {
|
||||
setSearchExpr(data.httpRequestLogFilter?.searchExpression || "");
|
||||
},
|
||||
});
|
||||
|
||||
const [
|
||||
setFilterMutate,
|
||||
{ error: setFilterErr, loading: setFilterLoading },
|
||||
] = useMutation<{
|
||||
const [setFilterMutate, { error: setFilterErr, loading: setFilterLoading }] = useMutation<{
|
||||
setHttpRequestLogFilter: SearchFilter | null;
|
||||
}>(SET_FILTER, {
|
||||
update(cache, { data: { setHttpRequestLogFilter } }) {
|
||||
update(cache, { data }) {
|
||||
cache.writeQuery({
|
||||
query: FILTER,
|
||||
data: {
|
||||
httpRequestLogFilter: setHttpRequestLogFilter,
|
||||
httpRequestLogFilter: data?.setHttpRequestLogFilter,
|
||||
},
|
||||
});
|
||||
},
|
||||
onError: () => {},
|
||||
});
|
||||
|
||||
const [
|
||||
clearHTTPRequestLog,
|
||||
clearHTTPRequestLogResult,
|
||||
] = useClearHTTPRequestLog();
|
||||
const [clearHTTPRequestLog, clearHTTPRequestLogResult] = useClearHTTPRequestLog();
|
||||
const clearHTTPConfirmationDialog = useConfirmationDialog();
|
||||
|
||||
const filterRef = useRef<HTMLElement | null>();
|
||||
const filterRef = useRef<HTMLFormElement>(null);
|
||||
const [filterOpen, setFilterOpen] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.SyntheticEvent) => {
|
||||
@ -133,8 +91,8 @@ function Search(): JSX.Element {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleClickAway = (event: React.MouseEvent<EventTarget>) => {
|
||||
if (filterRef.current.contains(event.target as HTMLElement)) {
|
||||
const handleClickAway = (event: MouseEvent | TouchEvent) => {
|
||||
if (filterRef?.current && filterRef.current.contains(event.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
setFilterOpen(false);
|
||||
@ -144,63 +102,67 @@ function Search(): JSX.Element {
|
||||
<Box>
|
||||
<Error prefix="Error fetching filter" error={filterErr} />
|
||||
<Error prefix="Error setting filter" error={setFilterErr} />
|
||||
<Error
|
||||
prefix="Error clearing all HTTP logs"
|
||||
error={clearHTTPRequestLogResult.error}
|
||||
/>
|
||||
<Error prefix="Error clearing all HTTP logs" error={clearHTTPRequestLogResult.error} />
|
||||
<Box style={{ display: "flex", flex: 1 }}>
|
||||
<ClickAwayListener onClickAway={handleClickAway}>
|
||||
<Paper
|
||||
component="form"
|
||||
onSubmit={handleSubmit}
|
||||
ref={filterRef}
|
||||
className={classes.root}
|
||||
sx={{
|
||||
padding: "2px 4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: 400,
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Toggle filter options">
|
||||
<IconButton
|
||||
className={classes.iconButton}
|
||||
onClick={() => setFilterOpen(!filterOpen)}
|
||||
style={{
|
||||
color: filter?.httpRequestLogFilter?.onlyInScope
|
||||
? theme.palette.secondary.main
|
||||
: "inherit",
|
||||
sx={{
|
||||
p: 1,
|
||||
color: filter?.httpRequestLogFilter?.onlyInScope ? "primary.main" : "inherit",
|
||||
}}
|
||||
>
|
||||
{filterLoading || setFilterLoading ? (
|
||||
<CircularProgress
|
||||
className={classes.filterLoading}
|
||||
size={23}
|
||||
/>
|
||||
<CircularProgress sx={{ color: theme.palette.text.primary }} size={23} />
|
||||
) : (
|
||||
<FilterListIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<InputBase
|
||||
className={classes.input}
|
||||
sx={{
|
||||
ml: 1,
|
||||
flex: 1,
|
||||
}}
|
||||
placeholder="Search proxy logs…"
|
||||
value={searchExpr}
|
||||
onChange={(e) => setSearchExpr(e.target.value)}
|
||||
onFocus={() => setFilterOpen(true)}
|
||||
/>
|
||||
<Tooltip title="Search">
|
||||
<IconButton type="submit" className={classes.iconButton}>
|
||||
<IconButton type="submit" sx={{ padding: 1.25 }}>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Popper
|
||||
className={classes.filterPopper}
|
||||
open={filterOpen}
|
||||
anchorEl={filterRef.current}
|
||||
placement="bottom-start"
|
||||
placement="bottom"
|
||||
style={{ zIndex: theme.zIndex.appBar }}
|
||||
>
|
||||
<Paper className={classes.filterOptions}>
|
||||
<Paper
|
||||
sx={{
|
||||
width: 400,
|
||||
marginTop: 0.5,
|
||||
p: 1.5,
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
filter?.httpRequestLogFilter?.onlyInScope ? true : false
|
||||
}
|
||||
checked={filter?.httpRequestLogFilter?.onlyInScope ? true : false}
|
||||
disabled={filterLoading || setFilterLoading}
|
||||
onChange={(e) =>
|
||||
setFilterMutate({
|
||||
|
Reference in New Issue
Block a user