diff options
Diffstat (limited to 'src/components/RequestDetails/RequestDetails.tsx')
| -rw-r--r-- | src/components/RequestDetails/RequestDetails.tsx | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/components/RequestDetails/RequestDetails.tsx b/src/components/RequestDetails/RequestDetails.tsx new file mode 100644 index 0000000..34e572c --- /dev/null +++ b/src/components/RequestDetails/RequestDetails.tsx | |||
| @@ -0,0 +1,166 @@ | |||
| 1 | import * as React from "react"; | ||
| 2 | import {useCallback, useMemo, useState} from "react"; | ||
| 3 | import {RequestResponse, Headers} from "../../hooks/useRequests"; | ||
| 4 | import styles from "./RequestDetails.module.scss"; | ||
| 5 | import RequestSummary from "../RequestSummary/RequestSummary"; | ||
| 6 | import Content from "../Content/Content"; | ||
| 7 | import {getHost} from "../../utils"; | ||
| 8 | import {Button, Card, Col, Container, Nav, Row, Table} from "react-bootstrap"; | ||
| 9 | |||
| 10 | |||
| 11 | interface TimingProps { | ||
| 12 | timing: number; | ||
| 13 | } | ||
| 14 | |||
| 15 | |||
| 16 | function Timing({ timing }: TimingProps) { | ||
| 17 | const value = useMemo(() => (Math.round(timing * 1000) / 1000), [timing]); | ||
| 18 | const showSeconds = useMemo(() => value > 1, [value]); | ||
| 19 | |||
| 20 | return !Number.isNaN(value) | ||
| 21 | ? ( | ||
| 22 | <> | ||
| 23 | {`${showSeconds ? value : value * 1000}${ showSeconds ? 's' : 'ms' }`} | ||
| 24 | </> | ||
| 25 | ) | ||
| 26 | : null; | ||
| 27 | } | ||
| 28 | |||
| 29 | interface HeadersProps { | ||
| 30 | title: string | ||
| 31 | headers: Headers | ||
| 32 | } | ||
| 33 | |||
| 34 | function Headers({ title, headers }: HeadersProps) { | ||
| 35 | return ( | ||
| 36 | <Card className="m-3"> | ||
| 37 | <Table | ||
| 38 | striped | ||
| 39 | responsive | ||
| 40 | borderless | ||
| 41 | hover | ||
| 42 | className='mb-0' | ||
| 43 | > | ||
| 44 | <thead> | ||
| 45 | <tr> | ||
| 46 | <th colSpan={2} className="bg-dark text-white rounded-top">{ title }</th> | ||
| 47 | </tr> | ||
| 48 | </thead> | ||
| 49 | <tbody> | ||
| 50 | { | ||
| 51 | headers.map(([key, value]) => ( | ||
| 52 | <tr> | ||
| 53 | <td>{key}</td> | ||
| 54 | <td>{value}</td> | ||
| 55 | </tr> | ||
| 56 | )) | ||
| 57 | } | ||
| 58 | </tbody> | ||
| 59 | </Table> | ||
| 60 | </Card> | ||
| 61 | ) | ||
| 62 | } | ||
| 63 | |||
| 64 | interface DetailsProps { | ||
| 65 | requestResponse: RequestResponse | null | ||
| 66 | } | ||
| 67 | |||
| 68 | type Tab = 'headers' | 'request' | 'response' | ||
| 69 | |||
| 70 | export default function RequestDetails({ requestResponse }: DetailsProps) { | ||
| 71 | const [tab, selectTab] = useState<Tab>('headers'); | ||
| 72 | const [raw, setRaw] = useState(false); | ||
| 73 | |||
| 74 | const resend = useCallback(async () => requestResponse !== null && fetch( | ||
| 75 | `http://${getHost()}/resend/`, | ||
| 76 | { | ||
| 77 | method: 'POST', | ||
| 78 | body: JSON.stringify({ | ||
| 79 | ...requestResponse.request, | ||
| 80 | id: undefined | ||
| 81 | }) | ||
| 82 | } | ||
| 83 | ), [requestResponse]); | ||
| 84 | |||
| 85 | return requestResponse !== null ? ( | ||
| 86 | <div className={styles.details}> | ||
| 87 | <div className={styles.header}> | ||
| 88 | <Row> | ||
| 89 | <Col> | ||
| 90 | <Container fluid style={{ fontSize: '1.5em' }} className="py-3"> | ||
| 91 | <RequestSummary requestResponse={requestResponse} className={styles.summary}/> | ||
| 92 | </Container> | ||
| 93 | </Col> | ||
| 94 | </Row> | ||
| 95 | <Row className="gx-0 d-flex"> | ||
| 96 | <Col> | ||
| 97 | <Nav | ||
| 98 | variant="tabs" | ||
| 99 | activeKey={tab} | ||
| 100 | onSelect={(tab) => selectTab(tab as Tab)} | ||
| 101 | > | ||
| 102 | <Nav.Item> | ||
| 103 | <Nav.Link eventKey="headers"> | ||
| 104 | Headers | ||
| 105 | </Nav.Link > | ||
| 106 | </Nav.Item> | ||
| 107 | <Nav.Item> | ||
| 108 | <Nav.Link eventKey="request"> | ||
| 109 | Request | ||
| 110 | </Nav.Link> | ||
| 111 | </Nav.Item> | ||
| 112 | <Nav.Item> | ||
| 113 | <Nav.Link | ||
| 114 | eventKey="response" | ||
| 115 | disabled={requestResponse.response === undefined} | ||
| 116 | > | ||
| 117 | Response | ||
| 118 | </Nav.Link> | ||
| 119 | </Nav.Item> | ||
| 120 | </Nav> | ||
| 121 | </Col> | ||
| 122 | <Col className="border-bottom px-3 " xs="auto"> | ||
| 123 | <Timing timing={requestResponse.response?.timing ?? NaN} /> | ||
| 124 | <Button variant="outline-primary" onClick={() => resend()} className="ms-3"> | ||
| 125 | Resend | ||
| 126 | </Button> | ||
| 127 | </Col> | ||
| 128 | </Row> | ||
| 129 | </div> | ||
| 130 | <div className={styles.content}> | ||
| 131 | { | ||
| 132 | tab === 'headers' && ( | ||
| 133 | <> | ||
| 134 | <Headers | ||
| 135 | title="request headers" | ||
| 136 | headers={requestResponse.request.headers} | ||
| 137 | /> | ||
| 138 | { | ||
| 139 | requestResponse.response && ( | ||
| 140 | <Headers | ||
| 141 | title="response headers" | ||
| 142 | headers={requestResponse.response.headers} | ||
| 143 | /> | ||
| 144 | ) | ||
| 145 | } | ||
| 146 | </> | ||
| 147 | ) | ||
| 148 | } | ||
| 149 | { | ||
| 150 | tab === 'request' && ( | ||
| 151 | <Content data={requestResponse.request} raw={raw} setRaw={setRaw}/> | ||
| 152 | ) | ||
| 153 | } | ||
| 154 | { | ||
| 155 | tab === 'response' && requestResponse.response !== undefined && ( | ||
| 156 | <Content data={requestResponse.response} raw={raw} setRaw={setRaw}/> | ||
| 157 | ) | ||
| 158 | } | ||
| 159 | </div> | ||
| 160 | </div> | ||
| 161 | ) : ( | ||
| 162 | <div className={styles.noRequestSelected}> | ||
| 163 | <p>Select a request to inspect it</p> | ||
| 164 | </div> | ||
| 165 | ) | ||
| 166 | } | ||
