mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Add initial UI/UX for intecepting requests
This commit is contained in:
@ -1,11 +1,12 @@
|
|||||||
|
import AltRouteIcon from "@mui/icons-material/AltRoute";
|
||||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||||
import FolderIcon from "@mui/icons-material/Folder";
|
import FolderIcon from "@mui/icons-material/Folder";
|
||||||
|
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
||||||
import HomeIcon from "@mui/icons-material/Home";
|
import HomeIcon from "@mui/icons-material/Home";
|
||||||
import LocationSearchingIcon from "@mui/icons-material/LocationSearching";
|
import LocationSearchingIcon from "@mui/icons-material/LocationSearching";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import SendIcon from "@mui/icons-material/Send";
|
import SendIcon from "@mui/icons-material/Send";
|
||||||
import SettingsEthernetIcon from "@mui/icons-material/SettingsEthernet";
|
|
||||||
import {
|
import {
|
||||||
Theme,
|
Theme,
|
||||||
useTheme,
|
useTheme,
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
CSSObject,
|
CSSObject,
|
||||||
Box,
|
Box,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
|
Badge,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
||||||
import MuiDrawer from "@mui/material/Drawer";
|
import MuiDrawer from "@mui/material/Drawer";
|
||||||
@ -28,10 +30,12 @@ import Link from "next/link";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useActiveProject } from "lib/ActiveProjectContext";
|
import { useActiveProject } from "lib/ActiveProjectContext";
|
||||||
|
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||||
|
|
||||||
export enum Page {
|
export enum Page {
|
||||||
Home,
|
Home,
|
||||||
GetStarted,
|
GetStarted,
|
||||||
|
Intercept,
|
||||||
Projects,
|
Projects,
|
||||||
ProxySetup,
|
ProxySetup,
|
||||||
ProxyLogs,
|
ProxyLogs,
|
||||||
@ -135,6 +139,7 @@ interface Props {
|
|||||||
|
|
||||||
export function Layout({ title, page, children }: Props): JSX.Element {
|
export function Layout({ title, page, children }: Props): JSX.Element {
|
||||||
const activeProject = useActiveProject();
|
const activeProject = useActiveProject();
|
||||||
|
const interceptedRequests = useInterceptedRequests();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
@ -204,12 +209,24 @@ export function Layout({ title, page, children }: Props): JSX.Element {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link href="/proxy/logs" passHref>
|
<Link href="/proxy/logs" passHref>
|
||||||
<ListItemButton key="proxyLogs" disabled={!activeProject} selected={page === Page.ProxyLogs}>
|
<ListItemButton key="proxyLogs" disabled={!activeProject} selected={page === Page.ProxyLogs}>
|
||||||
<Tooltip title="Proxy">
|
<Tooltip title="Proxy logs">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsEthernetIcon />
|
<FormatListBulletedIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<ListItemText primary="Proxy" />
|
<ListItemText primary="Logs" />
|
||||||
|
</ListItemButton>
|
||||||
|
</Link>
|
||||||
|
<Link href="/proxy/intercept" passHref>
|
||||||
|
<ListItemButton key="proxyIntercept" disabled={!activeProject} selected={page === Page.Intercept}>
|
||||||
|
<Tooltip title="Proxy intercept">
|
||||||
|
<ListItemIcon>
|
||||||
|
<Badge color="error" badgeContent={interceptedRequests?.length || 0}>
|
||||||
|
<AltRouteIcon />
|
||||||
|
</Badge>
|
||||||
|
</ListItemIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<ListItemText primary="Intercept" />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/sender" passHref>
|
<Link href="/sender" passHref>
|
||||||
|
203
admin/src/features/intercept/components/EditRequest.tsx
Normal file
203
admin/src/features/intercept/components/EditRequest.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import SendIcon from "@mui/icons-material/Send";
|
||||||
|
import { Alert, Box, Button, CircularProgress, Typography } from "@mui/material";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||||
|
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
||||||
|
import RequestTabs from "lib/components/RequestTabs";
|
||||||
|
import Response from "lib/components/Response";
|
||||||
|
import SplitPane from "lib/components/SplitPane";
|
||||||
|
import UrlBar, { HttpMethod, HttpProto, httpProtoMap } from "lib/components/UrlBar";
|
||||||
|
import {
|
||||||
|
HttpProtocol,
|
||||||
|
HttpRequest,
|
||||||
|
useGetInterceptedRequestQuery,
|
||||||
|
useModifyRequestMutation,
|
||||||
|
} from "lib/graphql/generated";
|
||||||
|
import { queryParamsFromURL } from "lib/queryParamsFromURL";
|
||||||
|
import updateKeyPairItem from "lib/updateKeyPairItem";
|
||||||
|
import updateURLQueryParams from "lib/updateURLQueryParams";
|
||||||
|
|
||||||
|
function EditRequest(): JSX.Element {
|
||||||
|
const router = useRouter();
|
||||||
|
const interceptedRequests = useInterceptedRequests();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If there's no request selected and there are pending reqs, navigate to
|
||||||
|
// the first one in the list. This helps you quickly review/handle reqs
|
||||||
|
// without having to manually select the next one in the requests table.
|
||||||
|
console.log(router.isReady, router.query.id, interceptedRequests?.length);
|
||||||
|
if (router.isReady && !router.query.id && interceptedRequests?.length) {
|
||||||
|
const req = interceptedRequests[0];
|
||||||
|
router.replace(`/proxy/intercept?id=${req.id}`);
|
||||||
|
}
|
||||||
|
}, [router, interceptedRequests]);
|
||||||
|
|
||||||
|
const reqId = router.query.id as string | undefined;
|
||||||
|
|
||||||
|
const [method, setMethod] = useState(HttpMethod.Get);
|
||||||
|
const [url, setURL] = useState("");
|
||||||
|
const [proto, setProto] = useState(HttpProto.Http20);
|
||||||
|
const [queryParams, setQueryParams] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
||||||
|
const [headers, setHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
||||||
|
const [body, setBody] = useState("");
|
||||||
|
|
||||||
|
const handleQueryParamChange = (key: string, value: string, idx: number) => {
|
||||||
|
setQueryParams((prev) => {
|
||||||
|
const updated = updateKeyPairItem(key, value, idx, prev);
|
||||||
|
setURL((prev) => updateURLQueryParams(prev, updated));
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleQueryParamDelete = (idx: number) => {
|
||||||
|
setQueryParams((prev) => {
|
||||||
|
const updated = prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length));
|
||||||
|
setURL((prev) => updateURLQueryParams(prev, updated));
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHeaderChange = (key: string, value: string, idx: number) => {
|
||||||
|
setHeaders((prev) => updateKeyPairItem(key, value, idx, prev));
|
||||||
|
};
|
||||||
|
const handleHeaderDelete = (idx: number) => {
|
||||||
|
setHeaders((prev) => prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleURLChange = (url: string) => {
|
||||||
|
setURL(url);
|
||||||
|
|
||||||
|
const questionMarkIndex = url.indexOf("?");
|
||||||
|
if (questionMarkIndex === -1) {
|
||||||
|
setQueryParams([{ key: "", value: "" }]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newQueryParams = queryParamsFromURL(url);
|
||||||
|
// Push empty row.
|
||||||
|
newQueryParams.push({ key: "", value: "" });
|
||||||
|
setQueryParams(newQueryParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReqResult = useGetInterceptedRequestQuery({
|
||||||
|
variables: { id: reqId as string },
|
||||||
|
skip: reqId === undefined,
|
||||||
|
onCompleted: ({ interceptedRequest }) => {
|
||||||
|
if (!interceptedRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setURL(interceptedRequest.url);
|
||||||
|
setMethod(interceptedRequest.method);
|
||||||
|
setBody(interceptedRequest.body || "");
|
||||||
|
|
||||||
|
const newQueryParams = queryParamsFromURL(interceptedRequest.url);
|
||||||
|
// Push empty row.
|
||||||
|
newQueryParams.push({ key: "", value: "" });
|
||||||
|
setQueryParams(newQueryParams);
|
||||||
|
|
||||||
|
const newHeaders = sortKeyValuePairs(interceptedRequest.headers || []);
|
||||||
|
setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const interceptedReq = reqId ? getReqResult?.data?.interceptedRequest : undefined;
|
||||||
|
|
||||||
|
const [modifyRequest, modifyResult] = useModifyRequestMutation();
|
||||||
|
|
||||||
|
const handleFormSubmit: React.FormEventHandler = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!interceptedReq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyRequest({
|
||||||
|
variables: {
|
||||||
|
request: {
|
||||||
|
id: interceptedReq.id,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
proto: httpProtoMap.get(proto) || HttpProtocol.Http20,
|
||||||
|
headers: headers.filter((kv) => kv.key !== ""),
|
||||||
|
body: body || undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update(cache) {
|
||||||
|
cache.modify({
|
||||||
|
fields: {
|
||||||
|
interceptedRequests(existing: HttpRequest[], { readField }) {
|
||||||
|
return existing.filter((ref) => interceptedReq.id !== readField("id", ref));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
setURL("");
|
||||||
|
setMethod(HttpMethod.Get);
|
||||||
|
setBody("");
|
||||||
|
setQueryParams([]);
|
||||||
|
setHeaders([]);
|
||||||
|
console.log("done!");
|
||||||
|
router.replace(`/proxy/intercept`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexDirection="column" height="100%" gap={2}>
|
||||||
|
<Box component="form" autoComplete="off" onSubmit={handleFormSubmit}>
|
||||||
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||||
|
<UrlBar
|
||||||
|
method={method}
|
||||||
|
onMethodChange={interceptedReq ? setMethod : undefined}
|
||||||
|
url={url.toString()}
|
||||||
|
onUrlChange={interceptedReq ? handleURLChange : undefined}
|
||||||
|
proto={proto}
|
||||||
|
onProtoChange={interceptedReq ? setProto : undefined}
|
||||||
|
sx={{ flex: "1 auto" }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disableElevation
|
||||||
|
type="submit"
|
||||||
|
disabled={!interceptedReq || modifyResult.loading}
|
||||||
|
startIcon={modifyResult.loading ? <CircularProgress size={22} /> : <SendIcon />}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{modifyResult.error && (
|
||||||
|
<Alert severity="error" sx={{ mt: 1 }}>
|
||||||
|
{modifyResult.error.message}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box flex="1 auto" position="relative">
|
||||||
|
<SplitPane split="vertical" size={"50%"}>
|
||||||
|
<Box sx={{ height: "100%", mr: 2, pb: 2, position: "relative" }}>
|
||||||
|
<Typography variant="overline" color="textSecondary" sx={{ position: "absolute", right: 0, mt: 1.2 }}>
|
||||||
|
Request
|
||||||
|
</Typography>
|
||||||
|
<RequestTabs
|
||||||
|
queryParams={interceptedReq ? queryParams : []}
|
||||||
|
headers={interceptedReq ? headers : []}
|
||||||
|
body={body}
|
||||||
|
onQueryParamChange={interceptedReq ? handleQueryParamChange : undefined}
|
||||||
|
onQueryParamDelete={interceptedReq ? handleQueryParamDelete : undefined}
|
||||||
|
onHeaderChange={interceptedReq ? handleHeaderChange : undefined}
|
||||||
|
onHeaderDelete={interceptedReq ? handleHeaderDelete : undefined}
|
||||||
|
onBodyChange={interceptedReq ? setBody : undefined}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ height: "100%", position: "relative", ml: 2, pb: 2 }}>
|
||||||
|
<Response response={null} />
|
||||||
|
</Box>
|
||||||
|
</SplitPane>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditRequest;
|
21
admin/src/features/intercept/components/Intercept.tsx
Normal file
21
admin/src/features/intercept/components/Intercept.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
import EditRequest from "./EditRequest";
|
||||||
|
import Requests from "./Requests";
|
||||||
|
|
||||||
|
import SplitPane from "lib/components/SplitPane";
|
||||||
|
|
||||||
|
export default function Sender(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Box sx={{ height: "100%", position: "relative" }}>
|
||||||
|
<SplitPane split="horizontal" size="70%">
|
||||||
|
<Box sx={{ width: "100%", pt: "0.75rem" }}>
|
||||||
|
<EditRequest />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ height: "100%", overflow: "scroll" }}>
|
||||||
|
<Requests />
|
||||||
|
</Box>
|
||||||
|
</SplitPane>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
33
admin/src/features/intercept/components/Requests.tsx
Normal file
33
admin/src/features/intercept/components/Requests.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Box, Paper, Typography } from "@mui/material";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||||
|
import RequestsTable from "lib/components/RequestsTable";
|
||||||
|
|
||||||
|
function Requests(): JSX.Element {
|
||||||
|
const interceptedRequests = useInterceptedRequests();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const activeId = router.query.id as string | undefined;
|
||||||
|
|
||||||
|
const handleRowClick = (id: string) => {
|
||||||
|
router.push(`/proxy/intercept?id=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{interceptedRequests && interceptedRequests.length > 0 && (
|
||||||
|
<RequestsTable requests={interceptedRequests} onRowClick={handleRowClick} activeRowId={activeId} />
|
||||||
|
)}
|
||||||
|
<Box sx={{ mt: 2, height: "100%" }}>
|
||||||
|
{interceptedRequests?.length === 0 && (
|
||||||
|
<Paper variant="centered">
|
||||||
|
<Typography>No pending intercepted requests.</Typography>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Requests;
|
@ -0,0 +1,13 @@
|
|||||||
|
query GetInterceptedRequest($id: ID!) {
|
||||||
|
interceptedRequest(id: $id) {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
method
|
||||||
|
proto
|
||||||
|
headers {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
mutation ModifyRequest($request: ModifyRequestInput!) {
|
||||||
|
modifyRequest(request: $request) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
57
admin/src/features/reqlog/components/Actions.tsx
Normal file
57
admin/src/features/reqlog/components/Actions.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import AltRouteIcon from "@mui/icons-material/AltRoute";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import { Alert } from "@mui/lab";
|
||||||
|
import { Badge, Button, IconButton, Tooltip } from "@mui/material";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||||
|
import { ConfirmationDialog, useConfirmationDialog } from "lib/components/ConfirmationDialog";
|
||||||
|
import { HttpRequestLogsDocument, useClearHttpRequestLogMutation } from "lib/graphql/generated";
|
||||||
|
|
||||||
|
function Actions(): JSX.Element {
|
||||||
|
const interceptedRequests = useInterceptedRequests();
|
||||||
|
const [clearHTTPRequestLog, clearLogsResult] = useClearHttpRequestLogMutation({
|
||||||
|
refetchQueries: [{ query: HttpRequestLogsDocument }],
|
||||||
|
});
|
||||||
|
const clearHTTPConfirmationDialog = useConfirmationDialog();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ConfirmationDialog
|
||||||
|
isOpen={clearHTTPConfirmationDialog.isOpen}
|
||||||
|
onClose={clearHTTPConfirmationDialog.close}
|
||||||
|
onConfirm={clearHTTPRequestLog}
|
||||||
|
>
|
||||||
|
All proxy logs are going to be removed. This action cannot be undone.
|
||||||
|
</ConfirmationDialog>
|
||||||
|
|
||||||
|
{clearLogsResult.error && <Alert severity="error">Failed to clear HTTP logs: {clearLogsResult.error}</Alert>}
|
||||||
|
|
||||||
|
<Link href="/proxy/intercept/?id=" passHref>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={interceptedRequests === null || interceptedRequests.length === 0}
|
||||||
|
color="primary"
|
||||||
|
component="a"
|
||||||
|
size="large"
|
||||||
|
startIcon={
|
||||||
|
<Badge color="error" badgeContent={interceptedRequests?.length || 0}>
|
||||||
|
<AltRouteIcon />
|
||||||
|
</Badge>
|
||||||
|
}
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
>
|
||||||
|
Review Intercepted…
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Tooltip title="Clear all">
|
||||||
|
<IconButton onClick={clearHTTPConfirmationDialog.open}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Actions;
|
@ -14,6 +14,7 @@ import {
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import Actions from "./Actions";
|
||||||
import LogDetail from "./LogDetail";
|
import LogDetail from "./LogDetail";
|
||||||
import Search from "./Search";
|
import Search from "./Search";
|
||||||
|
|
||||||
@ -94,7 +95,14 @@ export function RequestLogs(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex" flexDirection="column" height="100%">
|
<Box display="flex" flexDirection="column" height="100%">
|
||||||
<Search />
|
<Box display="flex">
|
||||||
|
<Box flex="1 auto">
|
||||||
|
<Search />
|
||||||
|
</Box>
|
||||||
|
<Box pt={0.5}>
|
||||||
|
<Actions />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box sx={{ display: "flex", flex: "1 auto", position: "relative" }}>
|
<Box sx={{ display: "flex", flex: "1 auto", position: "relative" }}>
|
||||||
<SplitPane split="horizontal" size={"40%"}>
|
<SplitPane split="horizontal" size={"40%"}>
|
||||||
<Box sx={{ width: "100%", height: "100%", pb: 2 }}>
|
<Box sx={{ width: "100%", height: "100%", pb: 2 }}>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import { Alert } from "@mui/lab";
|
import { Alert } from "@mui/lab";
|
||||||
@ -17,11 +16,8 @@ import {
|
|||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
|
|
||||||
import { ConfirmationDialog, useConfirmationDialog } from "lib/components/ConfirmationDialog";
|
|
||||||
import {
|
import {
|
||||||
HttpRequestLogFilterDocument,
|
HttpRequestLogFilterDocument,
|
||||||
HttpRequestLogsDocument,
|
|
||||||
useClearHttpRequestLogMutation,
|
|
||||||
useHttpRequestLogFilterQuery,
|
useHttpRequestLogFilterQuery,
|
||||||
useSetHttpRequestLogFilterMutation,
|
useSetHttpRequestLogFilterMutation,
|
||||||
} from "lib/graphql/generated";
|
} from "lib/graphql/generated";
|
||||||
@ -49,11 +45,6 @@ function Search(): JSX.Element {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [clearHTTPRequestLog, clearHTTPRequestLogResult] = useClearHttpRequestLogMutation({
|
|
||||||
refetchQueries: [{ query: HttpRequestLogsDocument }],
|
|
||||||
});
|
|
||||||
const clearHTTPConfirmationDialog = useConfirmationDialog();
|
|
||||||
|
|
||||||
const filterRef = useRef<HTMLFormElement>(null);
|
const filterRef = useRef<HTMLFormElement>(null);
|
||||||
const [filterOpen, setFilterOpen] = useState(false);
|
const [filterOpen, setFilterOpen] = useState(false);
|
||||||
|
|
||||||
@ -81,7 +72,6 @@ function Search(): JSX.Element {
|
|||||||
<Box>
|
<Box>
|
||||||
<Error prefix="Error fetching filter" error={filterResult.error} />
|
<Error prefix="Error fetching filter" error={filterResult.error} />
|
||||||
<Error prefix="Error setting filter" error={setFilterResult.error} />
|
<Error prefix="Error setting filter" error={setFilterResult.error} />
|
||||||
<Error prefix="Error clearing all HTTP logs" error={clearHTTPRequestLogResult.error} />
|
|
||||||
<Box style={{ display: "flex", flex: 1 }}>
|
<Box style={{ display: "flex", flex: 1 }}>
|
||||||
<ClickAwayListener onClickAway={handleClickAway}>
|
<ClickAwayListener onClickAway={handleClickAway}>
|
||||||
<Paper
|
<Paper
|
||||||
@ -161,21 +151,7 @@ function Search(): JSX.Element {
|
|||||||
</Popper>
|
</Popper>
|
||||||
</Paper>
|
</Paper>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
<Box style={{ marginLeft: "auto" }}>
|
|
||||||
<Tooltip title="Clear all">
|
|
||||||
<IconButton onClick={clearHTTPConfirmationDialog.open}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<ConfirmationDialog
|
|
||||||
isOpen={clearHTTPConfirmationDialog.isOpen}
|
|
||||||
onClose={clearHTTPConfirmationDialog.close}
|
|
||||||
onConfirm={clearHTTPRequestLog}
|
|
||||||
>
|
|
||||||
All proxy logs are going to be removed. This action cannot be undone.
|
|
||||||
</ConfirmationDialog>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,4 @@
|
|||||||
import {
|
import { Alert, Box, Button, Typography } from "@mui/material";
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
BoxProps,
|
|
||||||
Button,
|
|
||||||
InputLabel,
|
|
||||||
FormControl,
|
|
||||||
MenuItem,
|
|
||||||
Select,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
@ -17,76 +6,16 @@ import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
|||||||
import RequestTabs from "lib/components/RequestTabs";
|
import RequestTabs from "lib/components/RequestTabs";
|
||||||
import Response from "lib/components/Response";
|
import Response from "lib/components/Response";
|
||||||
import SplitPane from "lib/components/SplitPane";
|
import SplitPane from "lib/components/SplitPane";
|
||||||
|
import UrlBar, { HttpMethod, HttpProto, httpProtoMap } from "lib/components/UrlBar";
|
||||||
import {
|
import {
|
||||||
GetSenderRequestQuery,
|
GetSenderRequestQuery,
|
||||||
useCreateOrUpdateSenderRequestMutation,
|
useCreateOrUpdateSenderRequestMutation,
|
||||||
HttpProtocol,
|
|
||||||
useGetSenderRequestQuery,
|
useGetSenderRequestQuery,
|
||||||
useSendRequestMutation,
|
useSendRequestMutation,
|
||||||
} from "lib/graphql/generated";
|
} from "lib/graphql/generated";
|
||||||
import { queryParamsFromURL } from "lib/queryParamsFromURL";
|
import { queryParamsFromURL } from "lib/queryParamsFromURL";
|
||||||
|
import updateKeyPairItem from "lib/updateKeyPairItem";
|
||||||
enum HttpMethod {
|
import updateURLQueryParams from "lib/updateURLQueryParams";
|
||||||
Get = "GET",
|
|
||||||
Post = "POST",
|
|
||||||
Put = "PUT",
|
|
||||||
Patch = "PATCH",
|
|
||||||
Delete = "DELETE",
|
|
||||||
Head = "HEAD",
|
|
||||||
Options = "OPTIONS",
|
|
||||||
Connect = "CONNECT",
|
|
||||||
Trace = "TRACE",
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HttpProto {
|
|
||||||
Http10 = "HTTP/1.0",
|
|
||||||
Http11 = "HTTP/1.1",
|
|
||||||
Http20 = "HTTP/2.0",
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpProtoMap = new Map([
|
|
||||||
[HttpProto.Http10, HttpProtocol.Http10],
|
|
||||||
[HttpProto.Http11, HttpProtocol.Http11],
|
|
||||||
[HttpProto.Http20, HttpProtocol.Http20],
|
|
||||||
]);
|
|
||||||
|
|
||||||
function updateKeyPairItem(key: string, value: string, idx: number, items: KeyValuePair[]): KeyValuePair[] {
|
|
||||||
const updated = [...items];
|
|
||||||
updated[idx] = { key, value };
|
|
||||||
|
|
||||||
// Append an empty key-value pair if the last item in the array isn't blank
|
|
||||||
// anymore.
|
|
||||||
if (items.length - 1 === idx && items[idx].key === "" && items[idx].value === "") {
|
|
||||||
updated.push({ key: "", value: "" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateURLQueryParams(url: string, queryParams: KeyValuePair[]) {
|
|
||||||
// Note: We don't use the `URL` interface, because we're potentially dealing
|
|
||||||
// with malformed/incorrect URLs, which would yield TypeErrors when constructed
|
|
||||||
// via `URL`.
|
|
||||||
let newURL = url;
|
|
||||||
|
|
||||||
const questionMarkIndex = url.indexOf("?");
|
|
||||||
if (questionMarkIndex !== -1) {
|
|
||||||
newURL = newURL.slice(0, questionMarkIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
|
||||||
for (const { key, value } of queryParams.filter(({ key }) => key !== "")) {
|
|
||||||
searchParams.append(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawQueryParams = decodeURI(searchParams.toString());
|
|
||||||
|
|
||||||
if (rawQueryParams == "") {
|
|
||||||
return newURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newURL + "?" + rawQueryParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EditRequest(): JSX.Element {
|
function EditRequest(): JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -263,94 +192,4 @@ function EditRequest(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UrlBarProps extends BoxProps {
|
|
||||||
method: HttpMethod;
|
|
||||||
onMethodChange: (method: HttpMethod) => void;
|
|
||||||
url: string;
|
|
||||||
onUrlChange: (url: string) => void;
|
|
||||||
proto: HttpProto;
|
|
||||||
onProtoChange: (proto: HttpProto) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function UrlBar(props: UrlBarProps) {
|
|
||||||
const { method, onMethodChange, url, onUrlChange, proto, onProtoChange, ...other } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box {...other} sx={{ ...other.sx, display: "flex" }}>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel id="req-method-label">Method</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="req-method-label"
|
|
||||||
id="req-method"
|
|
||||||
value={method}
|
|
||||||
label="Method"
|
|
||||||
onChange={(e) => onMethodChange(e.target.value as HttpMethod)}
|
|
||||||
sx={{
|
|
||||||
width: "8rem",
|
|
||||||
".MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderRightWidth: 0,
|
|
||||||
borderTopRightRadius: 0,
|
|
||||||
borderBottomRightRadius: 0,
|
|
||||||
},
|
|
||||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderRightWidth: 1,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.values(HttpMethod).map((method) => (
|
|
||||||
<MenuItem key={method} value={method}>
|
|
||||||
{method}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<TextField
|
|
||||||
label="URL"
|
|
||||||
placeholder="E.g. “https://example.com/foobar”"
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => onUrlChange(e.target.value)}
|
|
||||||
required
|
|
||||||
variant="outlined"
|
|
||||||
InputLabelProps={{
|
|
||||||
shrink: true,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
sx: {
|
|
||||||
".MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderRadius: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
sx={{ flexGrow: 1 }}
|
|
||||||
/>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel id="req-proto-label">Protocol</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="req-proto-label"
|
|
||||||
id="req-proto"
|
|
||||||
value={proto}
|
|
||||||
label="Protocol"
|
|
||||||
onChange={(e) => onProtoChange(e.target.value as HttpProto)}
|
|
||||||
sx={{
|
|
||||||
".MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderLeftWidth: 0,
|
|
||||||
borderTopLeftRadius: 0,
|
|
||||||
borderBottomLeftRadius: 0,
|
|
||||||
},
|
|
||||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.values(HttpProto).map((proto) => (
|
|
||||||
<MenuItem key={proto} value={proto}>
|
|
||||||
{proto}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditRequest;
|
export default EditRequest;
|
||||||
|
22
admin/src/lib/InterceptedRequestsContext.tsx
Normal file
22
admin/src/lib/InterceptedRequestsContext.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React, { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
import { GetInterceptedRequestsQuery, useGetInterceptedRequestsQuery } from "./graphql/generated";
|
||||||
|
|
||||||
|
const InterceptedRequestsContext = createContext<GetInterceptedRequestsQuery["interceptedRequests"] | null>(null);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: React.ReactNode | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InterceptedRequestsProvider({ children }: Props): JSX.Element {
|
||||||
|
const { data } = useGetInterceptedRequestsQuery({
|
||||||
|
pollInterval: 1000,
|
||||||
|
});
|
||||||
|
const reqs = data?.interceptedRequests || null;
|
||||||
|
|
||||||
|
return <InterceptedRequestsContext.Provider value={reqs}>{children}</InterceptedRequestsContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInterceptedRequests() {
|
||||||
|
return useContext(InterceptedRequestsContext);
|
||||||
|
}
|
122
admin/src/lib/components/UrlBar.tsx
Normal file
122
admin/src/lib/components/UrlBar.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { Box, BoxProps, FormControl, InputLabel, MenuItem, Select, TextField } from "@mui/material";
|
||||||
|
|
||||||
|
import { HttpProtocol } from "lib/graphql/generated";
|
||||||
|
|
||||||
|
export enum HttpMethod {
|
||||||
|
Get = "GET",
|
||||||
|
Post = "POST",
|
||||||
|
Put = "PUT",
|
||||||
|
Patch = "PATCH",
|
||||||
|
Delete = "DELETE",
|
||||||
|
Head = "HEAD",
|
||||||
|
Options = "OPTIONS",
|
||||||
|
Connect = "CONNECT",
|
||||||
|
Trace = "TRACE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HttpProto {
|
||||||
|
Http10 = "HTTP/1.0",
|
||||||
|
Http11 = "HTTP/1.1",
|
||||||
|
Http20 = "HTTP/2.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const httpProtoMap = new Map([
|
||||||
|
[HttpProto.Http10, HttpProtocol.Http10],
|
||||||
|
[HttpProto.Http11, HttpProtocol.Http11],
|
||||||
|
[HttpProto.Http20, HttpProtocol.Http20],
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface UrlBarProps extends BoxProps {
|
||||||
|
method: HttpMethod;
|
||||||
|
onMethodChange?: (method: HttpMethod) => void;
|
||||||
|
url: string;
|
||||||
|
onUrlChange?: (url: string) => void;
|
||||||
|
proto: HttpProto;
|
||||||
|
onProtoChange?: (proto: HttpProto) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UrlBar(props: UrlBarProps) {
|
||||||
|
const { method, onMethodChange, url, onUrlChange, proto, onProtoChange, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box {...other} sx={{ ...other.sx, display: "flex" }}>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel id="req-method-label">Method</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="req-method-label"
|
||||||
|
id="req-method"
|
||||||
|
value={method}
|
||||||
|
label="Method"
|
||||||
|
disabled={!onMethodChange}
|
||||||
|
onChange={(e) => onMethodChange && onMethodChange(e.target.value as HttpMethod)}
|
||||||
|
sx={{
|
||||||
|
width: "8rem",
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderRightWidth: 0,
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderRightWidth: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(HttpMethod).map((method) => (
|
||||||
|
<MenuItem key={method} value={method}>
|
||||||
|
{method}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
label="URL"
|
||||||
|
placeholder="E.g. “https://example.com/foobar”"
|
||||||
|
value={url}
|
||||||
|
disabled={!onUrlChange}
|
||||||
|
onChange={(e) => onUrlChange && onUrlChange(e.target.value)}
|
||||||
|
required
|
||||||
|
variant="outlined"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
sx: {
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
sx={{ flexGrow: 1 }}
|
||||||
|
/>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel id="req-proto-label">Protocol</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="req-proto-label"
|
||||||
|
id="req-proto"
|
||||||
|
value={proto}
|
||||||
|
label="Protocol"
|
||||||
|
disabled={!onProtoChange}
|
||||||
|
onChange={(e) => onProtoChange && onProtoChange(e.target.value as HttpProto)}
|
||||||
|
sx={{
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderLeftWidth: 0,
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
},
|
||||||
|
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(HttpProto).map((proto) => (
|
||||||
|
<MenuItem key={proto} value={proto}>
|
||||||
|
{proto}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UrlBar;
|
@ -67,6 +67,16 @@ export enum HttpProtocol {
|
|||||||
Http20 = 'HTTP20'
|
Http20 = 'HTTP20'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HttpRequest = {
|
||||||
|
__typename?: 'HttpRequest';
|
||||||
|
body?: Maybe<Scalars['String']>;
|
||||||
|
headers: Array<HttpHeader>;
|
||||||
|
id: Scalars['ID'];
|
||||||
|
method: HttpMethod;
|
||||||
|
proto: HttpProtocol;
|
||||||
|
url: Scalars['URL'];
|
||||||
|
};
|
||||||
|
|
||||||
export type HttpRequestLog = {
|
export type HttpRequestLog = {
|
||||||
__typename?: 'HttpRequestLog';
|
__typename?: 'HttpRequestLog';
|
||||||
body?: Maybe<Scalars['String']>;
|
body?: Maybe<Scalars['String']>;
|
||||||
@ -101,6 +111,20 @@ export type HttpResponseLog = {
|
|||||||
statusReason: Scalars['String'];
|
statusReason: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ModifyRequestInput = {
|
||||||
|
body?: InputMaybe<Scalars['String']>;
|
||||||
|
headers?: InputMaybe<Array<HttpHeaderInput>>;
|
||||||
|
id: Scalars['ID'];
|
||||||
|
method: HttpMethod;
|
||||||
|
proto: HttpProtocol;
|
||||||
|
url: Scalars['URL'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModifyRequestResult = {
|
||||||
|
__typename?: 'ModifyRequestResult';
|
||||||
|
success: Scalars['Boolean'];
|
||||||
|
};
|
||||||
|
|
||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
clearHTTPRequestLog: ClearHttpRequestLogResult;
|
clearHTTPRequestLog: ClearHttpRequestLogResult;
|
||||||
@ -110,6 +134,7 @@ export type Mutation = {
|
|||||||
createSenderRequestFromHttpRequestLog: SenderRequest;
|
createSenderRequestFromHttpRequestLog: SenderRequest;
|
||||||
deleteProject: DeleteProjectResult;
|
deleteProject: DeleteProjectResult;
|
||||||
deleteSenderRequests: DeleteSenderRequestsResult;
|
deleteSenderRequests: DeleteSenderRequestsResult;
|
||||||
|
modifyRequest: ModifyRequestResult;
|
||||||
openProject?: Maybe<Project>;
|
openProject?: Maybe<Project>;
|
||||||
sendRequest: SenderRequest;
|
sendRequest: SenderRequest;
|
||||||
setHttpRequestLogFilter?: Maybe<HttpRequestLogFilter>;
|
setHttpRequestLogFilter?: Maybe<HttpRequestLogFilter>;
|
||||||
@ -138,6 +163,11 @@ export type MutationDeleteProjectArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationModifyRequestArgs = {
|
||||||
|
request: ModifyRequestInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationOpenProjectArgs = {
|
export type MutationOpenProjectArgs = {
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
};
|
};
|
||||||
@ -175,6 +205,8 @@ export type Query = {
|
|||||||
httpRequestLog?: Maybe<HttpRequestLog>;
|
httpRequestLog?: Maybe<HttpRequestLog>;
|
||||||
httpRequestLogFilter?: Maybe<HttpRequestLogFilter>;
|
httpRequestLogFilter?: Maybe<HttpRequestLogFilter>;
|
||||||
httpRequestLogs: Array<HttpRequestLog>;
|
httpRequestLogs: Array<HttpRequestLog>;
|
||||||
|
interceptedRequest?: Maybe<HttpRequest>;
|
||||||
|
interceptedRequests: Array<HttpRequest>;
|
||||||
projects: Array<Project>;
|
projects: Array<Project>;
|
||||||
scope: Array<ScopeRule>;
|
scope: Array<ScopeRule>;
|
||||||
senderRequest?: Maybe<SenderRequest>;
|
senderRequest?: Maybe<SenderRequest>;
|
||||||
@ -187,6 +219,11 @@ export type QueryHttpRequestLogArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryInterceptedRequestArgs = {
|
||||||
|
id: Scalars['ID'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QuerySenderRequestArgs = {
|
export type QuerySenderRequestArgs = {
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
};
|
};
|
||||||
@ -248,6 +285,20 @@ export type SenderRequestInput = {
|
|||||||
url: Scalars['URL'];
|
url: Scalars['URL'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetInterceptedRequestQueryVariables = Exact<{
|
||||||
|
id: Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetInterceptedRequestQuery = { __typename?: 'Query', interceptedRequest?: { __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod, proto: HttpProtocol, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }> } | null };
|
||||||
|
|
||||||
|
export type ModifyRequestMutationVariables = Exact<{
|
||||||
|
request: ModifyRequestInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ModifyRequestMutation = { __typename?: 'Mutation', modifyRequest: { __typename?: 'ModifyRequestResult', success: boolean } };
|
||||||
|
|
||||||
export type CloseProjectMutationVariables = Exact<{ [key: string]: never; }>;
|
export type CloseProjectMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@ -353,7 +404,88 @@ export type GetSenderRequestsQueryVariables = Exact<{ [key: string]: never; }>;
|
|||||||
|
|
||||||
export type GetSenderRequestsQuery = { __typename?: 'Query', senderRequests: Array<{ __typename?: 'SenderRequest', id: string, url: any, method: HttpMethod, response?: { __typename?: 'HttpResponseLog', id: string, statusCode: number, statusReason: string } | null }> };
|
export type GetSenderRequestsQuery = { __typename?: 'Query', senderRequests: Array<{ __typename?: 'SenderRequest', id: string, url: any, method: HttpMethod, response?: { __typename?: 'HttpResponseLog', id: string, statusCode: number, statusReason: string } | null }> };
|
||||||
|
|
||||||
|
export type GetInterceptedRequestsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetInterceptedRequestsQuery = { __typename?: 'Query', interceptedRequests: Array<{ __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod }> };
|
||||||
|
|
||||||
|
|
||||||
|
export const GetInterceptedRequestDocument = gql`
|
||||||
|
query GetInterceptedRequest($id: ID!) {
|
||||||
|
interceptedRequest(id: $id) {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
method
|
||||||
|
proto
|
||||||
|
headers {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGetInterceptedRequestQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGetInterceptedRequestQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGetInterceptedRequestQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useGetInterceptedRequestQuery({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGetInterceptedRequestQuery(baseOptions: Apollo.QueryHookOptions<GetInterceptedRequestQuery, GetInterceptedRequestQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GetInterceptedRequestQuery, GetInterceptedRequestQueryVariables>(GetInterceptedRequestDocument, options);
|
||||||
|
}
|
||||||
|
export function useGetInterceptedRequestLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetInterceptedRequestQuery, GetInterceptedRequestQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GetInterceptedRequestQuery, GetInterceptedRequestQueryVariables>(GetInterceptedRequestDocument, options);
|
||||||
|
}
|
||||||
|
export type GetInterceptedRequestQueryHookResult = ReturnType<typeof useGetInterceptedRequestQuery>;
|
||||||
|
export type GetInterceptedRequestLazyQueryHookResult = ReturnType<typeof useGetInterceptedRequestLazyQuery>;
|
||||||
|
export type GetInterceptedRequestQueryResult = Apollo.QueryResult<GetInterceptedRequestQuery, GetInterceptedRequestQueryVariables>;
|
||||||
|
export const ModifyRequestDocument = gql`
|
||||||
|
mutation ModifyRequest($request: ModifyRequestInput!) {
|
||||||
|
modifyRequest(request: $request) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ModifyRequestMutationFn = Apollo.MutationFunction<ModifyRequestMutation, ModifyRequestMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useModifyRequestMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useModifyRequestMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useModifyRequestMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [modifyRequestMutation, { data, loading, error }] = useModifyRequestMutation({
|
||||||
|
* variables: {
|
||||||
|
* request: // value for 'request'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useModifyRequestMutation(baseOptions?: Apollo.MutationHookOptions<ModifyRequestMutation, ModifyRequestMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ModifyRequestMutation, ModifyRequestMutationVariables>(ModifyRequestDocument, options);
|
||||||
|
}
|
||||||
|
export type ModifyRequestMutationHookResult = ReturnType<typeof useModifyRequestMutation>;
|
||||||
|
export type ModifyRequestMutationResult = Apollo.MutationResult<ModifyRequestMutation>;
|
||||||
|
export type ModifyRequestMutationOptions = Apollo.BaseMutationOptions<ModifyRequestMutation, ModifyRequestMutationVariables>;
|
||||||
export const CloseProjectDocument = gql`
|
export const CloseProjectDocument = gql`
|
||||||
mutation CloseProject {
|
mutation CloseProject {
|
||||||
closeProject {
|
closeProject {
|
||||||
@ -982,4 +1114,40 @@ export function useGetSenderRequestsLazyQuery(baseOptions?: Apollo.LazyQueryHook
|
|||||||
}
|
}
|
||||||
export type GetSenderRequestsQueryHookResult = ReturnType<typeof useGetSenderRequestsQuery>;
|
export type GetSenderRequestsQueryHookResult = ReturnType<typeof useGetSenderRequestsQuery>;
|
||||||
export type GetSenderRequestsLazyQueryHookResult = ReturnType<typeof useGetSenderRequestsLazyQuery>;
|
export type GetSenderRequestsLazyQueryHookResult = ReturnType<typeof useGetSenderRequestsLazyQuery>;
|
||||||
export type GetSenderRequestsQueryResult = Apollo.QueryResult<GetSenderRequestsQuery, GetSenderRequestsQueryVariables>;
|
export type GetSenderRequestsQueryResult = Apollo.QueryResult<GetSenderRequestsQuery, GetSenderRequestsQueryVariables>;
|
||||||
|
export const GetInterceptedRequestsDocument = gql`
|
||||||
|
query GetInterceptedRequests {
|
||||||
|
interceptedRequests {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGetInterceptedRequestsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGetInterceptedRequestsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGetInterceptedRequestsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useGetInterceptedRequestsQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGetInterceptedRequestsQuery(baseOptions?: Apollo.QueryHookOptions<GetInterceptedRequestsQuery, GetInterceptedRequestsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GetInterceptedRequestsQuery, GetInterceptedRequestsQueryVariables>(GetInterceptedRequestsDocument, options);
|
||||||
|
}
|
||||||
|
export function useGetInterceptedRequestsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetInterceptedRequestsQuery, GetInterceptedRequestsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GetInterceptedRequestsQuery, GetInterceptedRequestsQueryVariables>(GetInterceptedRequestsDocument, options);
|
||||||
|
}
|
||||||
|
export type GetInterceptedRequestsQueryHookResult = ReturnType<typeof useGetInterceptedRequestsQuery>;
|
||||||
|
export type GetInterceptedRequestsLazyQueryHookResult = ReturnType<typeof useGetInterceptedRequestsLazyQuery>;
|
||||||
|
export type GetInterceptedRequestsQueryResult = Apollo.QueryResult<GetInterceptedRequestsQuery, GetInterceptedRequestsQueryVariables>;
|
7
admin/src/lib/graphql/interceptedRequests.graphql
Normal file
7
admin/src/lib/graphql/interceptedRequests.graphql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
query GetInterceptedRequests {
|
||||||
|
interceptedRequests {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
method
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,19 @@ function createApolloClient() {
|
|||||||
link: new HttpLink({
|
link: new HttpLink({
|
||||||
uri: "/api/graphql/",
|
uri: "/api/graphql/",
|
||||||
}),
|
}),
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache({
|
||||||
|
typePolicies: {
|
||||||
|
Query: {
|
||||||
|
fields: {
|
||||||
|
interceptedRequests: {
|
||||||
|
merge(_, incoming) {
|
||||||
|
return incoming;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
admin/src/lib/updateKeyPairItem.ts
Normal file
16
admin/src/lib/updateKeyPairItem.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { KeyValuePair } from "./components/KeyValuePair";
|
||||||
|
|
||||||
|
function updateKeyPairItem(key: string, value: string, idx: number, items: KeyValuePair[]): KeyValuePair[] {
|
||||||
|
const updated = [...items];
|
||||||
|
updated[idx] = { key, value };
|
||||||
|
|
||||||
|
// Append an empty key-value pair if the last item in the array isn't blank
|
||||||
|
// anymore.
|
||||||
|
if (items.length - 1 === idx && items[idx].key === "" && items[idx].value === "") {
|
||||||
|
updated.push({ key: "", value: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateKeyPairItem;
|
28
admin/src/lib/updateURLQueryParams.ts
Normal file
28
admin/src/lib/updateURLQueryParams.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { KeyValuePair } from "./components/KeyValuePair";
|
||||||
|
|
||||||
|
function updateURLQueryParams(url: string, queryParams: KeyValuePair[]) {
|
||||||
|
// Note: We don't use the `URL` interface, because we're potentially dealing
|
||||||
|
// with malformed/incorrect URLs, which would yield TypeErrors when constructed
|
||||||
|
// via `URL`.
|
||||||
|
let newURL = url;
|
||||||
|
|
||||||
|
const questionMarkIndex = url.indexOf("?");
|
||||||
|
if (questionMarkIndex !== -1) {
|
||||||
|
newURL = newURL.slice(0, questionMarkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
for (const { key, value } of queryParams.filter(({ key }) => key !== "")) {
|
||||||
|
searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawQueryParams = decodeURI(searchParams.toString());
|
||||||
|
|
||||||
|
if (rawQueryParams == "") {
|
||||||
|
return newURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newURL + "?" + rawQueryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateURLQueryParams;
|
@ -7,6 +7,7 @@ import Head from "next/head";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { ActiveProjectProvider } from "lib/ActiveProjectContext";
|
import { ActiveProjectProvider } from "lib/ActiveProjectContext";
|
||||||
|
import { InterceptedRequestsProvider } from "lib/InterceptedRequestsContext";
|
||||||
import { useApollo } from "lib/graphql/useApollo";
|
import { useApollo } from "lib/graphql/useApollo";
|
||||||
import createEmotionCache from "lib/mui/createEmotionCache";
|
import createEmotionCache from "lib/mui/createEmotionCache";
|
||||||
import theme from "lib/mui/theme";
|
import theme from "lib/mui/theme";
|
||||||
@ -32,10 +33,12 @@ export default function MyApp(props: MyAppProps) {
|
|||||||
</Head>
|
</Head>
|
||||||
<ApolloProvider client={apolloClient}>
|
<ApolloProvider client={apolloClient}>
|
||||||
<ActiveProjectProvider>
|
<ActiveProjectProvider>
|
||||||
<ThemeProvider theme={theme}>
|
<InterceptedRequestsProvider>
|
||||||
<CssBaseline />
|
<ThemeProvider theme={theme}>
|
||||||
<Component {...pageProps} />
|
<CssBaseline />
|
||||||
</ThemeProvider>
|
<Component {...pageProps} />
|
||||||
|
</ThemeProvider>
|
||||||
|
</InterceptedRequestsProvider>
|
||||||
</ActiveProjectProvider>
|
</ActiveProjectProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
|
12
admin/src/pages/proxy/intercept/index.tsx
Normal file
12
admin/src/pages/proxy/intercept/index.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Layout, Page } from "features/Layout";
|
||||||
|
import Intercept from "features/intercept/components/Intercept";
|
||||||
|
|
||||||
|
function ProxyIntercept(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Layout page={Page.Intercept} title="Proxy intercept">
|
||||||
|
<Intercept />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProxyIntercept;
|
@ -10,9 +10,9 @@
|
|||||||
"@jridgewell/trace-mapping" "^0.3.0"
|
"@jridgewell/trace-mapping" "^0.3.0"
|
||||||
|
|
||||||
"@apollo/client@^3.2.0":
|
"@apollo/client@^3.2.0":
|
||||||
version "3.5.8"
|
version "3.5.10"
|
||||||
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.5.8.tgz#7215b974c5988b6157530eb69369209210349fe0"
|
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.5.10.tgz#43463108a6e07ae602cca0afc420805a19339a71"
|
||||||
integrity sha512-MAm05+I1ullr64VLpZwon/ISnkMuNLf6vDqgo9wiMhHYBGT4yOAbAIseRdjCHZwfSx/7AUuBgaTNOssZPIr6FQ==
|
integrity sha512-tL3iSpFe9Oldq7gYikZK1dcYxp1c01nlSwtsMz75382HcI6fvQXyFXUCJTTK3wgO2/ckaBvRGw7VqjFREdVoRw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@graphql-typed-document-node/core" "^3.0.0"
|
"@graphql-typed-document-node/core" "^3.0.0"
|
||||||
"@wry/context" "^0.6.0"
|
"@wry/context" "^0.6.0"
|
||||||
|
@ -131,6 +131,7 @@ type ComplexityRoot struct {
|
|||||||
HTTPRequestLog func(childComplexity int, id ulid.ULID) int
|
HTTPRequestLog func(childComplexity int, id ulid.ULID) int
|
||||||
HTTPRequestLogFilter func(childComplexity int) int
|
HTTPRequestLogFilter func(childComplexity int) int
|
||||||
HTTPRequestLogs func(childComplexity int) int
|
HTTPRequestLogs func(childComplexity int) int
|
||||||
|
InterceptedRequest func(childComplexity int, id ulid.ULID) int
|
||||||
InterceptedRequests func(childComplexity int) int
|
InterceptedRequests func(childComplexity int) int
|
||||||
Projects func(childComplexity int) int
|
Projects func(childComplexity int) int
|
||||||
Scope func(childComplexity int) int
|
Scope func(childComplexity int) int
|
||||||
@ -192,6 +193,7 @@ type QueryResolver interface {
|
|||||||
SenderRequest(ctx context.Context, id ulid.ULID) (*SenderRequest, error)
|
SenderRequest(ctx context.Context, id ulid.ULID) (*SenderRequest, error)
|
||||||
SenderRequests(ctx context.Context) ([]SenderRequest, error)
|
SenderRequests(ctx context.Context) ([]SenderRequest, error)
|
||||||
InterceptedRequests(ctx context.Context) ([]HTTPRequest, error)
|
InterceptedRequests(ctx context.Context) ([]HTTPRequest, error)
|
||||||
|
InterceptedRequest(ctx context.Context, id ulid.ULID) (*HTTPRequest, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type executableSchema struct {
|
type executableSchema struct {
|
||||||
@ -607,6 +609,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Query.HTTPRequestLogs(childComplexity), true
|
return e.complexity.Query.HTTPRequestLogs(childComplexity), true
|
||||||
|
|
||||||
|
case "Query.interceptedRequest":
|
||||||
|
if e.complexity.Query.InterceptedRequest == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := ec.field_Query_interceptedRequest_args(context.TODO(), rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Query.InterceptedRequest(childComplexity, args["id"].(ulid.ULID)), true
|
||||||
|
|
||||||
case "Query.interceptedRequests":
|
case "Query.interceptedRequests":
|
||||||
if e.complexity.Query.InterceptedRequests == nil {
|
if e.complexity.Query.InterceptedRequests == nil {
|
||||||
break
|
break
|
||||||
@ -973,6 +987,7 @@ type Query {
|
|||||||
senderRequest(id: ID!): SenderRequest
|
senderRequest(id: ID!): SenderRequest
|
||||||
senderRequests: [SenderRequest!]!
|
senderRequests: [SenderRequest!]!
|
||||||
interceptedRequests: [HttpRequest!]!
|
interceptedRequests: [HttpRequest!]!
|
||||||
|
interceptedRequest(id: ID!): HttpRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@ -1202,6 +1217,21 @@ func (ec *executionContext) field_Query_httpRequestLog_args(ctx context.Context,
|
|||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) field_Query_interceptedRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
var err error
|
||||||
|
args := map[string]interface{}{}
|
||||||
|
var arg0 ulid.ULID
|
||||||
|
if tmp, ok := rawArgs["id"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
|
||||||
|
arg0, err = ec.unmarshalNID2githubᚗcomᚋoklogᚋulidᚐULID(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["id"] = arg0
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) field_Query_senderRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
func (ec *executionContext) field_Query_senderRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]interface{}{}
|
args := map[string]interface{}{}
|
||||||
@ -3190,6 +3220,45 @@ func (ec *executionContext) _Query_interceptedRequests(ctx context.Context, fiel
|
|||||||
return ec.marshalNHttpRequest2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestᚄ(ctx, field.Selections, res)
|
return ec.marshalNHttpRequest2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Query_interceptedRequest(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "Query",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: true,
|
||||||
|
IsResolver: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
rawArgs := field.ArgumentMap(ec.Variables)
|
||||||
|
args, err := ec.field_Query_interceptedRequest_args(ctx, rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
fc.Args = args
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return ec.resolvers.Query().InterceptedRequest(rctx, args["id"].(ulid.ULID))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*HTTPRequest)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOHttpRequest2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequest(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -5805,6 +5874,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
case "interceptedRequest":
|
||||||
|
field := field
|
||||||
|
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
res = ec._Query_interceptedRequest(ctx, field)
|
||||||
|
return res
|
||||||
|
})
|
||||||
case "__type":
|
case "__type":
|
||||||
out.Values[i] = ec._Query___type(ctx, field)
|
out.Values[i] = ec._Query___type(ctx, field)
|
||||||
case "__schema":
|
case "__schema":
|
||||||
@ -7117,6 +7197,13 @@ func (ec *executionContext) marshalOHttpProtocol2ᚖgithubᚗcomᚋdstotijnᚋhe
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOHttpRequest2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequest(ctx context.Context, sel ast.SelectionSet, v *HTTPRequest) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._HttpRequest(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalOHttpRequestLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestLog(ctx context.Context, sel ast.SelectionSet, v *HTTPRequestLog) graphql.Marshaler {
|
func (ec *executionContext) marshalOHttpRequestLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPRequestLog(ctx context.Context, sel ast.SelectionSet, v *HTTPRequestLog) graphql.Marshaler {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
|
@ -541,6 +541,22 @@ func (r *queryResolver) InterceptedRequests(ctx context.Context) ([]HTTPRequest,
|
|||||||
return httpReqs, nil
|
return httpReqs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) InterceptedRequest(ctx context.Context, id ulid.ULID) (*HTTPRequest, error) {
|
||||||
|
req, err := r.InterceptService.RequestByID(id)
|
||||||
|
if errors.Is(err, intercept.ErrRequestNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get request by ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := parseHTTPRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httpReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyRequestInput) (*ModifyRequestResult, error) {
|
func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyRequestInput) (*ModifyRequestResult, error) {
|
||||||
body := ""
|
body := ""
|
||||||
if input.Body != nil {
|
if input.Body != nil {
|
||||||
|
@ -148,6 +148,7 @@ type Query {
|
|||||||
senderRequest(id: ID!): SenderRequest
|
senderRequest(id: ID!): SenderRequest
|
||||||
senderRequests: [SenderRequest!]!
|
senderRequests: [SenderRequest!]!
|
||||||
interceptedRequests: [HttpRequest!]!
|
interceptedRequests: [HttpRequest!]!
|
||||||
|
interceptedRequest(id: ID!): HttpRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
@ -176,6 +176,19 @@ func (svc *Service) Requests() []*http.Request {
|
|||||||
return reqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request returns an intercepted request by ID. It's safe for concurrent use.
|
||||||
|
func (svc *Service) RequestByID(id ulid.ULID) (*http.Request, error) {
|
||||||
|
svc.mu.RLock()
|
||||||
|
defer svc.mu.RUnlock()
|
||||||
|
|
||||||
|
req, ok := svc.requests[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrRequestNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return req.req, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ids RequestIDs) Len() int {
|
func (ids RequestIDs) Len() int {
|
||||||
return len(ids)
|
return len(ids)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user