= {
- px: 0,
- py: 0.33,
- verticalAlign: "top",
- border: "none",
- whiteSpace: "nowrap",
- overflow: "hidden",
- textOverflow: "ellipsis",
- "&:hover": {
- color: "primary.main",
- whiteSpace: "inherit",
- 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 [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);
- windowSel.removeAllRanges();
- windowSel.addRange(r);
- document.execCommand("copy");
- windowSel.removeAllRanges();
-
- setOpen(true);
- };
-
- const handleClose = (event: Event | React.SyntheticEvent, reason?: string) => {
- if (reason === "clickaway") {
- return;
- }
-
- setOpen(false);
- };
-
- return (
-
-
-
- Copied to clipboard.
-
-
-
-
-
- {headers.map(({ key, value }, index) => (
-
-
- {key}:
-
-
- {value}
-
-
- ))}
-
-
-
-
- );
-}
-
-export default HttpHeadersTable;
diff --git a/admin/src/features/reqlog/components/LogDetail.tsx b/admin/src/features/reqlog/components/LogDetail.tsx
index f672bb1..5416bd9 100644
--- a/admin/src/features/reqlog/components/LogDetail.tsx
+++ b/admin/src/features/reqlog/components/LogDetail.tsx
@@ -1,18 +1,20 @@
import Alert from "@mui/lab/Alert";
-import { Box, Grid, Paper, CircularProgress } from "@mui/material";
+import { Box, CircularProgress, Paper, Typography } from "@mui/material";
import RequestDetail from "./RequestDetail";
-import ResponseDetail from "./ResponseDetail";
+import Response from "lib/components/Response";
+import SplitPane from "lib/components/SplitPane";
import { useHttpRequestLogQuery } from "lib/graphql/generated";
interface Props {
- requestId: string;
+ id?: string;
}
-function LogDetail({ requestId: id }: Props): JSX.Element {
+function LogDetail({ id }: Props): JSX.Element {
const { loading, error, data } = useHttpRequestLogQuery({
- variables: { id },
+ variables: { id: id as string },
+ skip: id === undefined,
});
if (loading) {
@@ -31,28 +33,24 @@ function LogDetail({ requestId: id }: Props): JSX.Element {
}
if (!data?.httpRequestLog) {
- return ;
+ return (
+
+ Select a log entry…
+
+ );
}
- const httpRequestLog = data.httpRequestLog;
+ const reqLog = data.httpRequestLog;
return (
-
-
-
-
-
-
-
-
- {httpRequestLog.response && (
-
-
-
- )}
-
-
-
+
+
+ {reqLog.response && (
+
+
+
+ )}
+
);
}
diff --git a/admin/src/features/reqlog/components/LogsOverview.tsx b/admin/src/features/reqlog/components/LogsOverview.tsx
deleted file mode 100644
index 9bba0cd..0000000
--- a/admin/src/features/reqlog/components/LogsOverview.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import Alert from "@mui/lab/Alert";
-import { Box, CircularProgress, Link as MaterialLink, Typography } from "@mui/material";
-import Link from "next/link";
-import { useRouter } from "next/router";
-
-import LogDetail from "./LogDetail";
-import RequestList from "./RequestList";
-
-import CenteredPaper from "lib/components/CenteredPaper";
-import { useHttpRequestLogsQuery } from "lib/graphql/generated";
-
-export default function LogsOverview(): JSX.Element {
- const router = useRouter();
- const detailReqLogId = router.query.id as string | undefined;
- const { loading, error, data } = useHttpRequestLogsQuery({
- pollInterval: 1000,
- });
-
- const handleLogClick = (reqId: string) => {
- router.push("/proxy/logs?id=" + reqId, undefined, {
- shallow: false,
- });
- };
-
- if (loading) {
- return ;
- }
- if (error) {
- if (error.graphQLErrors[0]?.extensions?.code === "no_active_project") {
- return (
-
- There is no project active.{" "}
-
- Create or open
- {" "}
- one first.
-
- );
- }
- return Error fetching logs: {error.message};
- }
-
- const logs = data?.httpRequestLogs || [];
-
- return (
-
-
-
-
-
- {detailReqLogId && }
- {logs.length !== 0 && !detailReqLogId && (
-
- Select a log entry…
-
- )}
-
-
- );
-}
diff --git a/admin/src/features/reqlog/components/RequestDetail.tsx b/admin/src/features/reqlog/components/RequestDetail.tsx
index f7afedd..48c60e6 100644
--- a/admin/src/features/reqlog/components/RequestDetail.tsx
+++ b/admin/src/features/reqlog/components/RequestDetail.tsx
@@ -1,51 +1,46 @@
-import { Typography, Box, Divider } from "@mui/material";
+import { Typography, Box } from "@mui/material";
import React from "react";
-import HttpHeadersTable from "./HttpHeadersTable";
-
-import Editor from "lib/components/Editor";
+import RequestTabs from "lib/components/RequestTabs";
import { HttpRequestLogQuery } from "lib/graphql/generated";
+import { queryParamsFromURL } from "lib/queryParamsFromURL";
interface Props {
request: NonNullable;
}
function RequestDetail({ request }: Props): JSX.Element {
- const { method, url, proto, headers, body } = request;
+ const { method, url, headers, body } = request;
- const contentType = headers.find((header) => header.key === "Content-Type")?.value;
const parsedUrl = new URL(url);
return (
-
-
+
+
Request
- {method} {decodeURIComponent(parsedUrl.pathname + parsedUrl.search)}{" "}
-
- {proto}
-
+ {method} {decodeURIComponent(parsedUrl.pathname + parsedUrl.search)}
-
-
-
-
+
+
-
- {body && }
-
+
);
}
diff --git a/admin/src/features/reqlog/components/RequestList.tsx b/admin/src/features/reqlog/components/RequestList.tsx
deleted file mode 100644
index 5079ada..0000000
--- a/admin/src/features/reqlog/components/RequestList.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import {
- TableContainer,
- Paper,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Typography,
- Box,
- useTheme,
- MenuItem,
- Snackbar,
- Alert,
- Link,
-} from "@mui/material";
-import React, { useState } from "react";
-
-import CenteredPaper from "lib/components/CenteredPaper";
-import HttpStatusIcon from "lib/components/HttpStatusIcon";
-import useContextMenu from "lib/components/useContextMenu";
-import { HttpRequestLogsQuery, useCreateSenderRequestFromHttpRequestLogMutation } from "lib/graphql/generated";
-
-interface Props {
- logs: NonNullable;
- selectedReqLogId?: string;
- onLogClick(requestId: string): void;
-}
-
-export default function RequestList({ logs, onLogClick, selectedReqLogId }: Props): JSX.Element {
- return (
-
-
- {logs.length === 0 && (
-
-
- No logs found.
-
-
- )}
-
- );
-}
-
-interface RequestListTableProps {
- logs: HttpRequestLogsQuery["httpRequestLogs"];
- selectedReqLogId?: string;
- onLogClick(requestId: string): void;
-}
-
-function RequestListTable({ logs, selectedReqLogId, onLogClick }: RequestListTableProps): JSX.Element {
- const theme = useTheme();
-
- const [createSenderReqFromLog] = useCreateSenderRequestFromHttpRequestLogMutation({});
-
- const [copyToSenderId, setCopyToSenderId] = useState("");
- const [Menu, handleContextMenu, handleContextMenuClose] = useContextMenu();
-
- const handleCopyToSenderClick = () => {
- createSenderReqFromLog({
- variables: {
- id: copyToSenderId,
- },
- onCompleted({ createSenderRequestFromHttpRequestLog }) {
- const { id } = createSenderRequestFromHttpRequestLog;
- setNewSenderReqId(id);
- setCopiedReqNotifOpen(true);
- },
- });
- handleContextMenuClose();
- };
-
- const [newSenderReqId, setNewSenderReqId] = useState("");
- const [copiedReqNotifOpen, setCopiedReqNotifOpen] = useState(false);
- const handleCloseCopiedNotif = (_: Event | React.SyntheticEvent, reason?: string) => {
- if (reason === "clickaway") {
- return;
- }
- setCopiedReqNotifOpen(false);
- };
-
- return (
-
-
-
-
- Request was copied. Edit in Sender.
-
-
-
-
-
-
-
- Method
- Origin
- Path
- Status
-
-
-
- {logs.map(({ id, method, url, response }) => {
- const { origin, pathname, search, hash } = new URL(url);
-
- const cellStyle = {
- whiteSpace: "nowrap",
- overflow: "hidden",
- textOverflow: "ellipsis",
- };
-
- return (
- onLogClick(id)}
- onContextMenu={(e) => {
- setCopyToSenderId(id);
- handleContextMenu(e);
- }}
- >
-
- {method}
-
- {origin}
-
- {decodeURIComponent(pathname + search + hash)}
-
-
- {response && (
-
- {" "}
-
- {response.statusCode} {response.statusReason}
-
-
- )}
-
-
- );
- })}
-
-
-
-
- );
-}
diff --git a/admin/src/features/reqlog/components/RequestLogs.tsx b/admin/src/features/reqlog/components/RequestLogs.tsx
new file mode 100644
index 0000000..df505b0
--- /dev/null
+++ b/admin/src/features/reqlog/components/RequestLogs.tsx
@@ -0,0 +1,90 @@
+import { Alert, Box, Link, MenuItem, Snackbar } from "@mui/material";
+import { useRouter } from "next/router";
+import { useState } from "react";
+
+import LogDetail from "./LogDetail";
+import Search from "./Search";
+
+import RequestsTable from "lib/components/RequestsTable";
+import SplitPane from "lib/components/SplitPane";
+import useContextMenu from "lib/components/useContextMenu";
+import { useCreateSenderRequestFromHttpRequestLogMutation, useHttpRequestLogsQuery } from "lib/graphql/generated";
+
+export function RequestLogs(): JSX.Element {
+ const router = useRouter();
+ const id = router.query.id as string | undefined;
+ const { data } = useHttpRequestLogsQuery({
+ pollInterval: 1000,
+ });
+
+ const [createSenderReqFromLog] = useCreateSenderRequestFromHttpRequestLogMutation({});
+
+ const [copyToSenderId, setCopyToSenderId] = useState("");
+ const [Menu, handleContextMenu, handleContextMenuClose] = useContextMenu();
+
+ const handleCopyToSenderClick = () => {
+ createSenderReqFromLog({
+ variables: {
+ id: copyToSenderId,
+ },
+ onCompleted({ createSenderRequestFromHttpRequestLog }) {
+ const { id } = createSenderRequestFromHttpRequestLog;
+ setNewSenderReqId(id);
+ setCopiedReqNotifOpen(true);
+ },
+ });
+ handleContextMenuClose();
+ };
+
+ const [newSenderReqId, setNewSenderReqId] = useState("");
+ const [copiedReqNotifOpen, setCopiedReqNotifOpen] = useState(false);
+ const handleCloseCopiedNotif = (_: Event | React.SyntheticEvent, reason?: string) => {
+ if (reason === "clickaway") {
+ return;
+ }
+ setCopiedReqNotifOpen(false);
+ };
+
+ const handleRowClick = (id: string) => {
+ router.push(`/proxy/logs?id=${id}`);
+ };
+
+ const handleRowContextClick = (e: React.MouseEvent, id: string) => {
+ setCopyToSenderId(id);
+ handleContextMenu(e);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ Request was copied. Edit in Sender.
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/admin/src/features/reqlog/components/ResponseDetail.tsx b/admin/src/features/reqlog/components/ResponseDetail.tsx
deleted file mode 100644
index 7b839f3..0000000
--- a/admin/src/features/reqlog/components/ResponseDetail.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Typography, Box, Divider } from "@mui/material";
-
-import HttpHeadersTable from "./HttpHeadersTable";
-
-import Editor from "lib/components/Editor";
-import HttpStatusIcon from "lib/components/HttpStatusIcon";
-import { HttpRequestLogQuery } from "lib/graphql/generated";
-
-interface Props {
- response: NonNullable["response"]>;
-}
-
-function ResponseDetail({ response }: Props): JSX.Element {
- const contentType = response.headers.find((header) => header.key.toLowerCase() === "content-type")?.value;
-
- return (
-
-
-
- Response
-
-
- {" "}
-
-
- {response.proto}
-
- {" "}
- {response.statusCode} {response.statusReason}
-
-
-
-
-
-
-
-
-
- {response.body &&
}
-
- );
-}
-
-export default ResponseDetail;
diff --git a/admin/src/features/reqlog/components/Search.tsx b/admin/src/features/reqlog/components/Search.tsx
index 842771c..93406d4 100644
--- a/admin/src/features/reqlog/components/Search.tsx
+++ b/admin/src/features/reqlog/components/Search.tsx
@@ -17,8 +17,7 @@ import {
import IconButton from "@mui/material/IconButton";
import React, { useRef, useState } from "react";
-import { ConfirmationDialog, useConfirmationDialog } from "./ConfirmationDialog";
-
+import { ConfirmationDialog, useConfirmationDialog } from "lib/components/ConfirmationDialog";
import {
HttpRequestLogFilterDocument,
HttpRequestLogsDocument,
diff --git a/admin/src/features/reqlog/graphql/httpRequestLog.graphql b/admin/src/features/reqlog/graphql/httpRequestLog.graphql
index 1facdb8..d96aa63 100644
--- a/admin/src/features/reqlog/graphql/httpRequestLog.graphql
+++ b/admin/src/features/reqlog/graphql/httpRequestLog.graphql
@@ -10,6 +10,7 @@ query HttpRequestLog($id: ID!) {
}
body
response {
+ id
proto
headers {
key
diff --git a/admin/src/features/reqlog/index.ts b/admin/src/features/reqlog/index.ts
new file mode 100644
index 0000000..4e1de72
--- /dev/null
+++ b/admin/src/features/reqlog/index.ts
@@ -0,0 +1,3 @@
+import { RequestLogs } from "./components/RequestLogs";
+
+export default RequestLogs;
diff --git a/admin/src/features/sender/components/EditRequest.tsx b/admin/src/features/sender/components/EditRequest.tsx
index e903419..b2706c9 100644
--- a/admin/src/features/sender/components/EditRequest.tsx
+++ b/admin/src/features/sender/components/EditRequest.tsx
@@ -10,14 +10,13 @@ import {
TextField,
Typography,
} from "@mui/material";
-import { AllotmentProps, PaneProps } from "allotment/dist/types/src/allotment";
import { useRouter } from "next/router";
-import React, { useEffect, useRef, useState } from "react";
-
-import EditRequestTabs from "./EditRequestTabs";
-import { KeyValuePair, sortKeyValuePairs } from "./KeyValuePair";
-import Response from "./Response";
+import React, { useState } from "react";
+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 {
GetSenderRequestQuery,
useCreateOrUpdateSenderRequestMutation,
@@ -25,8 +24,7 @@ import {
useGetSenderRequestQuery,
useSendRequestMutation,
} from "lib/graphql/generated";
-
-import "allotment/dist/style.css";
+import { queryParamsFromURL } from "lib/queryParamsFromURL";
enum HttpMethod {
Get = "GET",
@@ -88,22 +86,6 @@ function updateURLQueryParams(url: string, queryParams: KeyValuePair[]) {
return newURL + "?" + rawQueryParams;
}
-function queryParamsFromURL(url: string): KeyValuePair[] {
- const questionMarkIndex = url.indexOf("?");
- if (questionMarkIndex === -1) {
- return [];
- }
-
- const queryParams: KeyValuePair[] = [];
-
- const searchParams = new URLSearchParams(url.slice(questionMarkIndex + 1));
- for (const [key, value] of searchParams) {
- queryParams.push({ key, value });
- }
-
- return queryParams;
-}
-
function EditRequest(): JSX.Element {
const router = useRouter();
const reqId = router.query.id as string | undefined;
@@ -219,28 +201,6 @@ function EditRequest(): JSX.Element {
createOrUpdateRequestAndSend();
};
- const isMountedRef = useRef(false);
- const [Allotment, setAllotment] = useState<
- (React.ComponentType & { Pane: React.ComponentType }) | null
- >(null);
- useEffect(() => {
- isMountedRef.current = true;
- import("allotment")
- .then((mod) => {
- if (!isMountedRef.current) {
- return;
- }
- setAllotment(mod.Allotment);
- })
- .catch((err) => console.error(err, `could not import allotment ${err.message}`));
- return () => {
- isMountedRef.current = false;
- };
- }, []);
- if (!Allotment) {
- return Loading...
;
- }
-
return (
@@ -276,31 +236,27 @@ function EditRequest(): JSX.Element {
)}
-
-
-
-
-
- Request
-
-
-
+
+
+
+
+ Request
+
+
-
-
-
-
+
+
-
+
);
diff --git a/admin/src/features/sender/components/History.tsx b/admin/src/features/sender/components/History.tsx
index 77fb610..b32d2d9 100644
--- a/admin/src/features/sender/components/History.tsx
+++ b/admin/src/features/sender/components/History.tsx
@@ -1,8 +1,7 @@
-import { TableContainer, Table, TableHead, TableRow, TableCell, Typography, Box, TableBody } from "@mui/material";
+import { Box, Paper, Typography } from "@mui/material";
import { useRouter } from "next/router";
-import CenteredPaper from "lib/components/CenteredPaper";
-import HttpStatusIcon from "lib/components/HttpStatusIcon";
+import RequestsTable from "lib/components/RequestsTable";
import { useGetSenderRequestsQuery } from "lib/graphql/generated";
function History(): JSX.Element {
@@ -20,71 +19,13 @@ function History(): JSX.Element {
return (
{!loading && data?.senderRequests && data?.senderRequests.length > 0 && (
-
-
-
-
- Method
- Origin
- Path
- Status
-
-
-
- {data?.senderRequests &&
- data.senderRequests.map(({ id, method, url, response }) => {
- const { origin, pathname, search, hash } = new URL(url);
-
- const cellStyle = {
- whiteSpace: "nowrap",
- overflow: "hidden",
- textOverflow: "ellipsis",
- };
-
- return (
- handleRowClick(id)}
- >
-
- {method}
-
- {origin}
-
- {decodeURIComponent(pathname + search + hash)}
-
-
- {response && (
-
- {" "}
-
- {response.statusCode} {response.statusReason}
-
-
- )}
-
-
- );
- })}
-
-
-
+
)}
{!loading && data?.senderRequests.length === 0 && (
-
+
No requests created yet.
-
+
)}
diff --git a/admin/src/features/sender/components/KeyValuePair.tsx b/admin/src/features/sender/components/KeyValuePair.tsx
deleted file mode 100644
index f4a57df..0000000
--- a/admin/src/features/sender/components/KeyValuePair.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import ClearIcon from "@mui/icons-material/Clear";
-import { IconButton, InputBase, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
-
-export interface KeyValuePair {
- key: string;
- value: string;
-}
-
-export interface KeyValuePairTableProps {
- items: KeyValuePair[];
- onChange?: (key: string, value: string, index: number) => void;
- onDelete?: (index: number) => void;
-}
-
-export function KeyValuePairTable({ items, onChange, onDelete }: KeyValuePairTableProps): JSX.Element {
- const inputSx = {
- fontSize: "0.875rem",
- "&.MuiInputBase-root input": {
- p: 0,
- },
- };
-
- return (
-
-
-
-
- Key
- Value
- {onDelete && }
-
-
-
- {items.map(({ key, value }, idx) => (
-
-
- {!onChange && {key}}
- {onChange && (
- {
- onChange && onChange(e.target.value, value, idx);
- }}
- sx={inputSx}
- />
- )}
-
-
- {!onChange && value}
- {onChange && (
- {
- onChange && onChange(key, e.target.value, idx);
- }}
- sx={inputSx}
- />
- )}
-
- {onDelete && (
-
-
- {
- onDelete && onDelete(idx);
- }}
- sx={{
- visibility: onDelete === undefined || items.length === idx + 1 ? "hidden" : "inherit",
- }}
- >
-
-
-
-
- )}
-
- ))}
-
-
-
- );
-}
-
-export function sortKeyValuePairs(items: KeyValuePair[]): KeyValuePair[] {
- const sorted = [...items];
-
- sorted.sort((a, b) => {
- if (a.key < b.key) {
- return -1;
- }
- if (a.key > b.key) {
- return 1;
- }
- return 0;
- });
-
- return sorted;
-}
-
-export default KeyValuePairTable;
diff --git a/admin/src/features/sender/components/Sender.tsx b/admin/src/features/sender/components/Sender.tsx
new file mode 100644
index 0000000..8bc315e
--- /dev/null
+++ b/admin/src/features/sender/components/Sender.tsx
@@ -0,0 +1,21 @@
+import { Box } from "@mui/material";
+
+import EditRequest from "./EditRequest";
+import History from "./History";
+
+import SplitPane from "lib/components/SplitPane";
+
+export default function Sender(): JSX.Element {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/admin/src/features/sender/index.ts b/admin/src/features/sender/index.ts
new file mode 100644
index 0000000..d6ed6bf
--- /dev/null
+++ b/admin/src/features/sender/index.ts
@@ -0,0 +1,3 @@
+import Sender from "./components/Sender";
+
+export default Sender;
diff --git a/admin/src/lib/ActiveProjectContext.tsx b/admin/src/lib/ActiveProjectContext.tsx
new file mode 100644
index 0000000..3bc35bc
--- /dev/null
+++ b/admin/src/lib/ActiveProjectContext.tsx
@@ -0,0 +1,20 @@
+import React, { createContext, useContext } from "react";
+
+import { Project, useProjectsQuery } from "./graphql/generated";
+
+const ActiveProjectContext = createContext(null);
+
+interface Props {
+ children?: React.ReactNode | undefined;
+}
+
+export function ActiveProjectProvider({ children }: Props): JSX.Element {
+ const { data } = useProjectsQuery();
+ const project = data?.projects.find((project) => project.isActive) || null;
+
+ return {children};
+}
+
+export function useActiveProject() {
+ return useContext(ActiveProjectContext);
+}
diff --git a/admin/src/lib/components/CenteredPaper.tsx b/admin/src/lib/components/CenteredPaper.tsx
deleted file mode 100644
index 2fa73b7..0000000
--- a/admin/src/lib/components/CenteredPaper.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Paper } from "@mui/material";
-
-function CenteredPaper({ children }: { children: React.ReactNode }): JSX.Element {
- return (
-
- );
-}
-
-export default CenteredPaper;
diff --git a/admin/src/features/reqlog/components/ConfirmationDialog.tsx b/admin/src/lib/components/ConfirmationDialog.tsx
similarity index 100%
rename from admin/src/features/reqlog/components/ConfirmationDialog.tsx
rename to admin/src/lib/components/ConfirmationDialog.tsx
diff --git a/admin/src/lib/components/KeyValuePair.tsx b/admin/src/lib/components/KeyValuePair.tsx
new file mode 100644
index 0000000..ce8167e
--- /dev/null
+++ b/admin/src/lib/components/KeyValuePair.tsx
@@ -0,0 +1,201 @@
+import ClearIcon from "@mui/icons-material/Clear";
+import {
+ Alert,
+ IconButton,
+ InputBase,
+ InputBaseProps,
+ Snackbar,
+ styled,
+ Table,
+ TableBody,
+ TableCell,
+ TableCellProps,
+ TableContainer,
+ TableHead,
+ TableRow,
+ TableRowProps,
+} from "@mui/material";
+import { useState } from "react";
+
+const StyledInputBase = styled(InputBase)(() => ({
+ fontSize: "0.875rem",
+ "&.MuiInputBase-root input": {
+ p: 0,
+ },
+}));
+
+const StyledTableRow = styled(TableRow)(() => ({
+ "& .delete-button": {
+ visibility: "hidden",
+ },
+ "&:hover .delete-button": {
+ visibility: "inherit",
+ },
+}));
+
+export interface KeyValuePair {
+ key: string;
+ value: string;
+}
+
+export interface KeyValuePairTableProps {
+ items: KeyValuePair[];
+ onChange?: (key: string, value: string, index: number) => void;
+ onDelete?: (index: number) => void;
+}
+
+export function KeyValuePairTable({ items, onChange, onDelete }: KeyValuePairTableProps): JSX.Element {
+ const [copyConfOpen, setCopyConfOpen] = useState(false);
+
+ const handleCellClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+
+ const windowSel = window.getSelection();
+
+ if (!windowSel || !document) {
+ return;
+ }
+
+ const r = document.createRange();
+ r.selectNode(e.currentTarget);
+ windowSel.removeAllRanges();
+ windowSel.addRange(r);
+ document.execCommand("copy");
+ windowSel.removeAllRanges();
+
+ setCopyConfOpen(true);
+ };
+
+ const handleCopyConfClose = (_: Event | React.SyntheticEvent, reason?: string) => {
+ if (reason === "clickaway") {
+ return;
+ }
+
+ setCopyConfOpen(false);
+ };
+
+ const baseCellStyle = {
+ "&:hover": {
+ cursor: "copy",
+ },
+ };
+
+ const KeyTableCell = styled(TableCell)(() => (!onChange ? baseCellStyle : {}));
+ const ValueTableCell = styled(TableCell)(() => ({
+ ...(!onChange && baseCellStyle),
+ width: "60%",
+ wordBreak: "break-all",
+ }));
+
+ return (
+
+
+
+ Copied to clipboard.
+
+
+
+
+
+
+ Key
+ Value
+ {onDelete && }
+
+
+
+ {items.map(({ key, value }, idx) => (
+
+ {
+ !onChange && handleCellClick(e);
+ }}
+ >
+ {!onChange && {key}}
+ {onChange && (
+ {
+ onChange && onChange(e.target.value, value, idx);
+ }}
+ />
+ )}
+
+ {
+ !onChange && handleCellClick(e);
+ }}
+ >
+ {!onChange && value}
+ {onChange && (
+ {
+ onChange && onChange(key, e.target.value, idx);
+ }}
+ />
+ )}
+
+ {onDelete && (
+
+
+ {
+ onDelete && onDelete(idx);
+ }}
+ sx={{
+ visibility: onDelete === undefined || items.length === idx + 1 ? "hidden" : "inherit",
+ }}
+ >
+
+
+
+
+ )}
+
+ ))}
+
+
+
+
+ );
+}
+
+export function sortKeyValuePairs(items: KeyValuePair[]): KeyValuePair[] {
+ const sorted = [...items];
+
+ sorted.sort((a, b) => {
+ if (a.key < b.key) {
+ return -1;
+ }
+ if (a.key > b.key) {
+ return 1;
+ }
+ return 0;
+ });
+
+ return sorted;
+}
+
+export default KeyValuePairTable;
diff --git a/admin/src/features/sender/components/EditRequestTabs.tsx b/admin/src/lib/components/RequestTabs.tsx
similarity index 60%
rename from admin/src/features/sender/components/EditRequestTabs.tsx
rename to admin/src/lib/components/RequestTabs.tsx
index 2742ff7..8aa9876 100644
--- a/admin/src/features/sender/components/EditRequestTabs.tsx
+++ b/admin/src/lib/components/RequestTabs.tsx
@@ -12,18 +12,18 @@ enum TabValue {
Body = "body",
}
-interface EditRequestTabsProps {
+interface RequestTabsProps {
queryParams: KeyValuePair[];
headers: KeyValuePair[];
- onQueryParamChange: KeyValuePairTableProps["onChange"];
- onQueryParamDelete: KeyValuePairTableProps["onDelete"];
- onHeaderChange: KeyValuePairTableProps["onChange"];
- onHeaderDelete: KeyValuePairTableProps["onDelete"];
- body: string;
- onBodyChange: (value: string) => void;
+ onQueryParamChange?: KeyValuePairTableProps["onChange"];
+ onQueryParamDelete?: KeyValuePairTableProps["onDelete"];
+ onHeaderChange?: KeyValuePairTableProps["onChange"];
+ onHeaderDelete?: KeyValuePairTableProps["onDelete"];
+ body?: string | null;
+ onBodyChange?: (value: string) => void;
}
-function EditRequestTabs(props: EditRequestTabsProps): JSX.Element {
+function RequestTabs(props: RequestTabsProps): JSX.Element {
const {
queryParams,
onQueryParamChange,
@@ -40,46 +40,45 @@ function EditRequestTabs(props: EditRequestTabsProps): JSX.Element {
textTransform: "none",
};
+ const queryParamsLength = onQueryParamChange ? queryParams.length - 1 : queryParams.length;
+ const headersLength = onHeaderChange ? headers.length - 1 : headers.length;
+
return (
-
+
-
+
setTabValue(value)}>
-
+
1 ? "s" : "") + ")" : "")}
+ label={"Body" + (body?.length ? ` (${body.length} byte` + (body.length > 1 ? "s" : "") + ")" : "")}
sx={tabSx}
/>
-
-
+
+
-
+
{
- onBodyChange(value || "");
+ onBodyChange && onBodyChange(value || "");
}}
- monacoOptions={{ readOnly: false }}
+ monacoOptions={{ readOnly: onBodyChange === undefined }}
contentType={headers.find(({ key }) => key.toLowerCase() === "content-type")?.value}
/>
@@ -89,4 +88,4 @@ function EditRequestTabs(props: EditRequestTabsProps): JSX.Element {
);
}
-export default EditRequestTabs;
+export default RequestTabs;
diff --git a/admin/src/lib/components/RequestsTable.tsx b/admin/src/lib/components/RequestsTable.tsx
new file mode 100644
index 0000000..baa9b17
--- /dev/null
+++ b/admin/src/lib/components/RequestsTable.tsx
@@ -0,0 +1,125 @@
+import {
+ TableContainer,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+ styled,
+ TableCellProps,
+ TableRowProps,
+} from "@mui/material";
+
+import HttpStatusIcon from "./HttpStatusIcon";
+
+import { HttpMethod } from "lib/graphql/generated";
+
+const baseCellStyle = {
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+} as const;
+
+const MethodTableCell = styled(TableCell)(() => ({
+ ...baseCellStyle,
+ width: "100px",
+}));
+
+const OriginTableCell = styled(TableCell)(() => ({
+ ...baseCellStyle,
+ maxWidth: "100px",
+}));
+
+const PathTableCell = styled(TableCell)(() => ({
+ ...baseCellStyle,
+ maxWidth: "200px",
+}));
+
+const StatusTableCell = styled(TableCell)(() => ({
+ ...baseCellStyle,
+ width: "100px",
+}));
+
+const RequestTableRow = styled(TableRow)(() => ({
+ "&:hover": {
+ cursor: "pointer",
+ },
+}));
+
+interface HttpRequest {
+ id: string;
+ url: string;
+ method: HttpMethod;
+ response?: HttpResponse | null;
+}
+
+interface HttpResponse {
+ statusCode: number;
+ statusReason: string;
+ body?: string;
+}
+
+interface Props {
+ requests: HttpRequest[];
+ activeRowId?: string;
+ onRowClick?: (id: string) => void;
+ onContextMenu?: (e: React.MouseEvent, id: string) => void;
+}
+
+export default function RequestsTable(props: Props): JSX.Element {
+ const { requests, activeRowId, onRowClick, onContextMenu } = props;
+
+ return (
+
+
+
+
+ Method
+ Origin
+ Path
+ Status
+
+
+
+ {requests.map(({ id, method, url, response }) => {
+ const { origin, pathname, search, hash } = new URL(url);
+
+ return (
+ {
+ onRowClick && onRowClick(id);
+ }}
+ onContextMenu={(e) => {
+ onContextMenu && onContextMenu(e, id);
+ }}
+ >
+
+ {method}
+
+ {origin}
+ {decodeURIComponent(pathname + search + hash)}
+
+ {response && }
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+function Status({ code, reason }: { code: number; reason: string }): JSX.Element {
+ return (
+
+ {" "}
+
+ {code} {reason}
+
+
+ );
+}
diff --git a/admin/src/features/sender/components/Response.tsx b/admin/src/lib/components/Response.tsx
similarity index 54%
rename from admin/src/features/sender/components/Response.tsx
rename to admin/src/lib/components/Response.tsx
index 22cec12..2f30f26 100644
--- a/admin/src/features/sender/components/Response.tsx
+++ b/admin/src/lib/components/Response.tsx
@@ -13,22 +13,20 @@ interface ResponseProps {
function Response({ response }: ResponseProps): JSX.Element {
return (
-
-
-
- Response
-
- {response && (
-
-
-
- )}
-
-
+
+
+ Response
+
+ {response && (
+
+
+
+ )}
+
+
Response not received yet.
-
+
);
function ResponseTabs(props: ResponseTabsProps): JSX.Element {
@@ -38,7 +36,7 @@ function ResponseTabs(props: ResponseTabsProps): JSX.Element {
return (
-
+
setTabValue(value)}>
(({ theme }) => ({
+ ".Resizer": {
+ zIndex: theme.zIndex.mobileStepper,
+ boxSizing: "border-box",
+ backgroundClip: "padding-box",
+ backgroundColor: alpha(theme.palette.grey[400], 0.05),
+ },
+ ".Resizer:hover": {
+ transition: "all 0.5s ease",
+ backgroundColor: alpha(theme.palette.primary.main, 1),
+ },
+
+ ".Resizer.horizontal": {
+ height: theme.spacing(SIZE_FACTOR),
+ marginTop: theme.spacing(MARGIN_FACTOR),
+ marginBottom: theme.spacing(MARGIN_FACTOR),
+ borderTop: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
+ borderBottom: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
+ borderBottomColor: "rgba(255, 255, 255, 0)",
+ cursor: "row-resize",
+ width: "100%",
+ },
+
+ ".Resizer.vertical": {
+ width: theme.spacing(SIZE_FACTOR),
+ marginLeft: theme.spacing(MARGIN_FACTOR),
+ marginRight: theme.spacing(MARGIN_FACTOR),
+ borderLeft: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
+ borderRight: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
+ cursor: "col-resize",
+ },
+
+ ".Resizer.disabled": {
+ cursor: "not-allowed",
+ },
+
+ ".Resizer.disabled:hover": {
+ borderColor: "transparent",
+ },
+
+ ".Pane": {
+ overflow: "hidden",
+ },
+}));
+
+export default SplitPane;
diff --git a/admin/src/lib/graphql/generated.tsx b/admin/src/lib/graphql/generated.tsx
index a08e322..ab0cfba 100644
--- a/admin/src/lib/graphql/generated.tsx
+++ b/admin/src/lib/graphql/generated.tsx
@@ -288,7 +288,7 @@ export type HttpRequestLogQueryVariables = Exact<{
}>;
-export type HttpRequestLogQuery = { __typename?: 'Query', httpRequestLog?: { __typename?: 'HttpRequestLog', id: string, method: HttpMethod, url: string, proto: string, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }>, response?: { __typename?: 'HttpResponseLog', proto: HttpProtocol, statusCode: number, statusReason: string, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }> } | null } | null };
+export type HttpRequestLogQuery = { __typename?: 'Query', httpRequestLog?: { __typename?: 'HttpRequestLog', id: string, method: HttpMethod, url: string, proto: string, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }>, response?: { __typename?: 'HttpResponseLog', id: string, proto: HttpProtocol, statusCode: number, statusReason: string, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }> } | null } | null };
export type HttpRequestLogFilterQueryVariables = Exact<{ [key: string]: never; }>;
@@ -568,6 +568,7 @@ export const HttpRequestLogDocument = gql`
}
body
response {
+ id
proto
headers {
key
diff --git a/admin/src/lib/mui/theme.ts b/admin/src/lib/mui/theme.ts
index ee3b81a..7920b86 100644
--- a/admin/src/lib/mui/theme.ts
+++ b/admin/src/lib/mui/theme.ts
@@ -1,6 +1,12 @@
import * as colors from "@mui/material/colors";
import { createTheme } from "@mui/material/styles";
+declare module "@mui/material/Paper" {
+ interface PaperPropsVariantOverrides {
+ centered: true;
+ }
+}
+
const heading = {
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
@@ -41,13 +47,28 @@ theme = createTheme(theme, {
},
},
components: {
- MuiTableCell: {
+ MuiTableRow: {
styleOverrides: {
- stickyHeader: {
- backgroundColor: theme.palette.secondary.dark,
+ root: {
+ "&.Mui-selected, &.Mui-selected:hover": {
+ backgroundColor: theme.palette.grey[700],
+ },
},
},
},
+ MuiPaper: {
+ variants: [
+ {
+ props: { variant: "centered" },
+ style: {
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ padding: theme.spacing(4),
+ },
+ },
+ ],
+ },
},
});
diff --git a/admin/src/lib/queryParamsFromURL.tsx b/admin/src/lib/queryParamsFromURL.tsx
new file mode 100644
index 0000000..6ce89a6
--- /dev/null
+++ b/admin/src/lib/queryParamsFromURL.tsx
@@ -0,0 +1,17 @@
+import { KeyValuePair } from "./components/KeyValuePair";
+
+export function queryParamsFromURL(url: string): KeyValuePair[] {
+ const questionMarkIndex = url.indexOf("?");
+ if (questionMarkIndex === -1) {
+ return [];
+ }
+
+ const queryParams: KeyValuePair[] = [];
+
+ const searchParams = new URLSearchParams(url.slice(questionMarkIndex + 1));
+ for (const [key, value] of searchParams) {
+ queryParams.push({ key, value });
+ }
+
+ return queryParams;
+}
diff --git a/admin/src/pages/_app.tsx b/admin/src/pages/_app.tsx
index 9cb4275..e7eda29 100644
--- a/admin/src/pages/_app.tsx
+++ b/admin/src/pages/_app.tsx
@@ -1,11 +1,12 @@
import { ApolloProvider } from "@apollo/client";
import { CacheProvider, EmotionCache } from "@emotion/react";
+import { ThemeProvider } from "@mui/material";
import CssBaseline from "@mui/material/CssBaseline";
-import { ThemeProvider } from "@mui/material/styles";
import { AppProps } from "next/app";
import Head from "next/head";
import React from "react";
+import { ActiveProjectProvider } from "lib/ActiveProjectContext";
import { useApollo } from "lib/graphql/useApollo";
import createEmotionCache from "lib/mui/createEmotionCache";
import theme from "lib/mui/theme";
@@ -30,10 +31,12 @@ export default function MyApp(props: MyAppProps) {
-
-
-
-
+
+
+
+
+
+
);
diff --git a/admin/src/pages/proxy/logs/index.tsx b/admin/src/pages/proxy/logs/index.tsx
index 447859e..97000d6 100644
--- a/admin/src/pages/proxy/logs/index.tsx
+++ b/admin/src/pages/proxy/logs/index.tsx
@@ -1,16 +1,10 @@
-import { Box } from "@mui/material";
-
import { Layout, Page } from "features/Layout";
-import LogsOverview from "features/reqlog/components/LogsOverview";
-import Search from "features/reqlog/components/Search";
+import RequestLogs from "features/reqlog";
function ProxyLogs(): JSX.Element {
return (
-
-
-
-
+
);
}
diff --git a/admin/src/pages/sender/index.tsx b/admin/src/pages/sender/index.tsx
index d141378..0a9373e 100644
--- a/admin/src/pages/sender/index.tsx
+++ b/admin/src/pages/sender/index.tsx
@@ -1,47 +1,10 @@
-import { Box } from "@mui/system";
-import { AllotmentProps } from "allotment";
-import { PaneProps } from "allotment/dist/types/src/allotment";
-import { ComponentType, useEffect, useRef, useState } from "react";
-
import { Layout, Page } from "features/Layout";
-import EditRequest from "features/sender/components/EditRequest";
-import History from "features/sender/components/History";
+import Sender from "features/sender";
function Index(): JSX.Element {
- const isMountedRef = useRef(false);
- const [Allotment, setAllotment] = useState<
- (ComponentType & { Pane: ComponentType }) | null
- >(null);
- useEffect(() => {
- isMountedRef.current = true;
- import("allotment")
- .then((mod) => {
- if (!isMountedRef.current) {
- return;
- }
- setAllotment(mod.Allotment);
- })
- .catch((err) => console.error(err, `could not import allotment ${err.message}`));
- return () => {
- isMountedRef.current = false;
- };
- }, []);
- if (!Allotment) {
- return Loading...
;
- }
-
return (
-
-
-
-
-
-
-
-
-
-
+
);
}
diff --git a/admin/yarn.lock b/admin/yarn.lock
index daa2a3e..f527df5 100644
--- a/admin/yarn.lock
+++ b/admin/yarn.lock
@@ -499,7 +499,7 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.17.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.8.3":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
@@ -1229,6 +1229,15 @@
"@mui/utils" "^5.3.0"
prop-types "^15.7.2"
+"@mui/private-theming@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.4.2.tgz#f0a05f908456a2f7b87ccb6fc3b6e1faae9d89e6"
+ integrity sha512-mlPDYYko4wIcwXjCPEmOWbNTT4DZ6h9YHdnRtQPnWM28+TRUHEo7SbydnnmVDQLRXUfaH4Y6XtEHIfBNPE/SLg==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@mui/utils" "^5.4.2"
+ prop-types "^15.7.2"
+
"@mui/styled-engine@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.3.0.tgz#b260a06398fc7335a62fd65ebbb9fc3c4071027b"
@@ -1238,6 +1247,29 @@
"@emotion/cache" "^11.7.1"
prop-types "^15.7.2"
+"@mui/styles@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.2.tgz#e0dadfc5de8255605f23c2f909f3669f0911bb88"
+ integrity sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@emotion/hash" "^0.8.0"
+ "@mui/private-theming" "^5.4.2"
+ "@mui/types" "^7.1.2"
+ "@mui/utils" "^5.4.2"
+ clsx "^1.1.1"
+ csstype "^3.0.10"
+ hoist-non-react-statics "^3.3.2"
+ jss "^10.8.2"
+ jss-plugin-camel-case "^10.8.2"
+ jss-plugin-default-unit "^10.8.2"
+ jss-plugin-global "^10.8.2"
+ jss-plugin-nested "^10.8.2"
+ jss-plugin-props-sort "^10.8.2"
+ jss-plugin-rule-value-function "^10.8.2"
+ jss-plugin-vendor-prefixer "^10.8.2"
+ prop-types "^15.7.2"
+
"@mui/system@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.3.0.tgz#cd2c5fd7631f2c90f0072c866015bb24e319b66e"
@@ -1257,6 +1289,11 @@
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.0.tgz#5ed928c5a41cfbf9a4be82ea3bbdc47bcc9610d5"
integrity sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ==
+"@mui/types@^7.1.2":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.2.tgz#4f3678ae77a7a3efab73b6e040469cc6df2144ac"
+ integrity sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ==
+
"@mui/utils@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.3.0.tgz#5f31915063d25c56f1d3ba9e289bf447472a868c"
@@ -1268,6 +1305,17 @@
prop-types "^15.7.2"
react-is "^17.0.2"
+"@mui/utils@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.4.2.tgz#3edda8f80de235418fff0424ee66e2a49793ec01"
+ integrity sha512-646dBCC57MXTo/Gf3AnZSHRHznaTETQq5x7AWp5FRQ4jPeyT4WSs18cpJVwkV01cAHKh06pNQTIufIALIWCL5g==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@types/prop-types" "^15.7.4"
+ "@types/react-is" "^16.7.1 || ^17.0.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
"@n1ru4l/graphql-live-query@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc"
@@ -2489,6 +2537,14 @@ cross-undici-fetch@^0.1.19:
undici "^4.9.3"
web-streams-polyfill "^3.2.0"
+css-vendor@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
+ integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
+ dependencies:
+ "@babel/runtime" "^7.8.3"
+ is-in-browser "^1.0.2"
+
csstype@^3.0.10, csstype@^3.0.2:
version "3.0.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
@@ -3490,6 +3546,11 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
+hyphenate-style-name@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
+ integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+
iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -3677,6 +3738,11 @@ is-glob@4.0.3, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-in-browser@^1.0.2, is-in-browser@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
+ integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
+
is-interactive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
@@ -3931,6 +3997,76 @@ jsonwebtoken@^8.5.1:
ms "^2.1.1"
semver "^5.6.0"
+jss-plugin-camel-case@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz#4921b568b38d893f39736ee8c4c5f1c64670aaf7"
+ integrity sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ hyphenate-style-name "^1.0.3"
+ jss "10.9.0"
+
+jss-plugin-default-unit@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz#bb23a48f075bc0ce852b4b4d3f7582bc002df991"
+ integrity sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
+jss-plugin-global@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz#fc07a0086ac97aca174e37edb480b69277f3931f"
+ integrity sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
+jss-plugin-nested@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz#cc1c7d63ad542c3ccc6e2c66c8328c6b6b00f4b3"
+ integrity sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+ tiny-warning "^1.0.2"
+
+jss-plugin-props-sort@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz#30e9567ef9479043feb6e5e59db09b4de687c47d"
+ integrity sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
+jss-plugin-rule-value-function@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz#379fd2732c0746fe45168011fe25544c1a295d67"
+ integrity sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+ tiny-warning "^1.0.2"
+
+jss-plugin-vendor-prefixer@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz#aa9df98abfb3f75f7ed59a3ec50a5452461a206a"
+ integrity sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ css-vendor "^2.0.8"
+ jss "10.9.0"
+
+jss@10.9.0, jss@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.yarnpkg.com/jss/-/jss-10.9.0.tgz#7583ee2cdc904a83c872ba695d1baab4b59c141b"
+ integrity sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ csstype "^3.0.2"
+ is-in-browser "^1.1.3"
+ tiny-warning "^1.0.2"
+
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"
@@ -4759,7 +4895,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -4822,11 +4958,32 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+ integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
+react-split-pane@^0.1.92:
+ version "0.1.92"
+ resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.92.tgz#68242f72138aed95dd5910eeb9d99822c4fc3a41"
+ integrity sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==
+ dependencies:
+ prop-types "^15.7.2"
+ react-lifecycles-compat "^3.0.4"
+ react-style-proptype "^3.2.2"
+
+react-style-proptype@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.2.2.tgz#d8e998e62ce79ec35b087252b90f19f1c33968a0"
+ integrity sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ==
+ dependencies:
+ prop-types "^15.5.4"
+
react-transition-group@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
@@ -5503,6 +5660,11 @@ through@^2.3.6, through@^2.3.8:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+tiny-warning@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+ integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
title-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"