Skip to content

Commit

Permalink
Clear all HTTP request logs (#49)
Browse files Browse the repository at this point in the history
* Create mutation to clear request logs

* Add UI for clearing all HTTP request logs

* Use consistent naming

* Explicitly delete only from http_requests

* Check if datebase is open

* Add confirmation dialog
  • Loading branch information
MichalZalecki authored Nov 28, 2020
1 parent efc115e commit e59b9d6
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 103 deletions.
53 changes: 53 additions & 0 deletions admin/src/components/reqlog/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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";

export function useConfirmationDialog() {
const [isOpen, setIsOpen] = useState(false);
const close = () => setIsOpen(false);
const open = () => setIsOpen(true);

return { open, close, isOpen };
}

interface ConfirmationDialog {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
children: React.ReactNode;
}

export function ConfirmationDialog(props: ConfirmationDialog) {
const { onClose, onConfirm, isOpen, children } = props;

function confirm() {
onConfirm();
onClose();
}

return (
<Dialog
open={isOpen}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{children}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Abort</Button>
<Button onClick={confirm} autoFocus>
Confirm
</Button>
</DialogActions>
</Dialog>
);
}
23 changes: 3 additions & 20 deletions admin/src/components/reqlog/LogsOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
import { useRouter } from "next/router";
import { gql, useQuery } from "@apollo/client";
import Link from "next/link";
import {
Box,
Typography,
CircularProgress,
Link as MaterialLink,
Typography,
} from "@material-ui/core";
import Alert from "@material-ui/lab/Alert";

import RequestList from "./RequestList";
import LogDetail from "./LogDetail";
import CenteredPaper from "../CenteredPaper";

const HTTP_REQUEST_LOGS = gql`
query HttpRequestLogs {
httpRequestLogs {
id
method
url
timestamp
response {
statusCode
statusReason
}
}
}
`;
import { useHttpRequestLogs } from "./hooks/useHttpRequestLogs";

function LogsOverview(): JSX.Element {
const router = useRouter();
const detailReqLogId =
router.query.id && parseInt(router.query.id as string, 10);

const { loading, error, data } = useQuery(HTTP_REQUEST_LOGS, {
pollInterval: 1000,
});
const { loading, error, data } = useHttpRequestLogs();

const handleLogClick = (reqId: number) => {
router.push("/proxy/logs?id=" + reqId, undefined, {
Expand Down
199 changes: 116 additions & 83 deletions admin/src/components/reqlog/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ import {
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";
import React, { useRef, useState } from "react";
import { gql, useApolloClient, useMutation, useQuery } from "@apollo/client";
import { gql, useMutation, useQuery } from "@apollo/client";
import { withoutTypename } from "../../lib/omitTypename";
import { Alert } from "@material-ui/lab";
import { useClearHTTPRequestLog } from "./hooks/useClearHTTPRequestLog";
import {
ConfirmationDialog,
useConfirmationDialog,
} from "./ConfirmationDialog";

const FILTER = gql`
query HttpRequestLogFilter {
Expand Down Expand Up @@ -79,15 +85,14 @@ function Search(): JSX.Element {
FILTER
);

const client = useApolloClient();
const [
setFilterMutate,
{ error: setFilterErr, loading: setFilterLoading },
] = useMutation<{
setHttpRequestLogFilter: SearchFilter | null;
}>(SET_FILTER, {
update(_, { data: { setHttpRequestLogFilter } }) {
client.writeQuery({
update(cache, { data: { setHttpRequestLogFilter } }) {
cache.writeQuery({
query: FILTER,
data: {
httpRequestLogFilter: setHttpRequestLogFilter,
Expand All @@ -96,6 +101,12 @@ function Search(): JSX.Element {
},
});

const [
clearHTTPRequestLog,
clearHTTPRequestLogResult,
] = useClearHTTPRequestLog();
const clearHTTPConfirmationDialog = useConfirmationDialog();

const filterRef = useRef<HTMLElement | null>();
const [filterOpen, setFilterOpen] = useState(false);

Expand All @@ -111,90 +122,112 @@ function Search(): JSX.Element {
};

return (
<ClickAwayListener onClickAway={handleClickAway}>
<Box style={{ display: "inline-block" }}>
{filterErr && (
<Box mb={4}>
<Alert severity="error">
Error fetching filter: {filterErr.message}
</Alert>
</Box>
)}
{setFilterErr && (
<Box mb={4}>
<Alert severity="error">
Error setting filter: {setFilterErr.message}
</Alert>
</Box>
)}
<Paper
component="form"
onSubmit={handleSubmit}
ref={filterRef}
className={classes.root}
>
<Tooltip title="Toggle filter options">
<IconButton
className={classes.iconButton}
onClick={() => setFilterOpen(!filterOpen)}
style={{
color:
filter?.httpRequestLogFilter !== null
? theme.palette.secondary.main
: "inherit",
}}
<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}
/>
<Box style={{ display: "flex", flex: 1 }}>
<ClickAwayListener onClickAway={handleClickAway}>
<Paper
component="form"
onSubmit={handleSubmit}
ref={filterRef}
className={classes.root}
>
<Tooltip title="Toggle filter options">
<IconButton
className={classes.iconButton}
onClick={() => setFilterOpen(!filterOpen)}
style={{
color:
filter?.httpRequestLogFilter !== null
? theme.palette.secondary.main
: "inherit",
}}
>
{filterLoading || setFilterLoading ? (
<CircularProgress
className={classes.filterLoading}
size={23}
/>
) : (
<FilterListIcon />
)}
</IconButton>
</Tooltip>
<InputBase
className={classes.input}
placeholder="Search proxy logs…"
onFocus={() => setFilterOpen(true)}
/>
<Tooltip title="Search">
<IconButton type="submit" className={classes.iconButton}>
<SearchIcon />
</IconButton>
</Tooltip>
<Popper
className={classes.filterPopper}
open={filterOpen}
anchorEl={filterRef.current}
placement="bottom-start"
>
{filterLoading || setFilterLoading ? (
<CircularProgress className={classes.filterLoading} size={23} />
) : (
<FilterListIcon />
)}
</IconButton>
</Tooltip>
<InputBase
className={classes.input}
placeholder="Search proxy logs…"
onFocus={() => setFilterOpen(true)}
/>
<Tooltip title="Search">
<IconButton type="submit" className={classes.iconButton}>
<SearchIcon />
</IconButton>
</Tooltip>
</Paper>

<Popper
className={classes.filterPopper}
open={filterOpen}
anchorEl={filterRef.current}
placement="bottom-start"
>
<Paper className={classes.filterOptions}>
<FormControlLabel
control={
<Checkbox
checked={
filter?.httpRequestLogFilter?.onlyInScope ? true : false
}
disabled={filterLoading || setFilterLoading}
onChange={(e) =>
setFilterMutate({
variables: {
filter: {
...withoutTypename(filter?.httpRequestLogFilter),
onlyInScope: e.target.checked,
},
},
})
<Paper className={classes.filterOptions}>
<FormControlLabel
control={
<Checkbox
checked={
filter?.httpRequestLogFilter?.onlyInScope ? true : false
}
disabled={filterLoading || setFilterLoading}
onChange={(e) =>
setFilterMutate({
variables: {
filter: {
...withoutTypename(filter?.httpRequestLogFilter),
onlyInScope: e.target.checked,
},
},
})
}
/>
}
label="Only show in-scope requests"
/>
}
label="Only show in-scope requests"
/>
</Paper>
</Popper>
</Paper>
</Popper>
</ClickAwayListener>
<Box style={{ marginLeft: "auto" }}>
<Tooltip title="Clear all">
<IconButton onClick={clearHTTPConfirmationDialog.open}>
<DeleteIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
</ClickAwayListener>
<ConfirmationDialog
isOpen={clearHTTPConfirmationDialog.isOpen}
onClose={clearHTTPConfirmationDialog.close}
onConfirm={clearHTTPRequestLog}
>
All proxy logs are going to be removed. This action cannot be undone.
</ConfirmationDialog>
</Box>
);
}

function Error(props: { prefix: string; error?: Error }) {
if (!props.error) return null;

return (
<Box mb={4}>
<Alert severity="error">
{props.prefix}: {props.error.message}
</Alert>
</Box>
);
}

Expand Down
16 changes: 16 additions & 0 deletions admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { gql, useMutation } from "@apollo/client";
import { HTTP_REQUEST_LOGS } from "./useHttpRequestLogs";

const CLEAR_HTTP_REQUEST_LOG = gql`
mutation ClearHTTPRequestLog {
clearHTTPRequestLog {
success
}
}
`;

export function useClearHTTPRequestLog() {
return useMutation(CLEAR_HTTP_REQUEST_LOG, {
refetchQueries: [{ query: HTTP_REQUEST_LOGS }],
});
}
22 changes: 22 additions & 0 deletions admin/src/components/reqlog/hooks/useHttpRequestLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { gql, useQuery } from "@apollo/client";

export const HTTP_REQUEST_LOGS = gql`
query HttpRequestLogs {
httpRequestLogs {
id
method
url
timestamp
response {
statusCode
statusReason
}
}
}
`;

export function useHttpRequestLogs() {
return useQuery(HTTP_REQUEST_LOGS, {
pollInterval: 1000,
});
}
Loading

0 comments on commit e59b9d6

Please sign in to comment.