aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2022-01-19 21:27:21 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2022-01-25 22:06:58 +0100
commit51a2ac628822e96459b3d570eada953ac8927d43 (patch)
tree7bb3766580f0fe88d5ff5786e1ad5a640704cc8d /src/components
parent1af1996d38ceaf2b96dcd5b1a6ee0a9c158022f8 (diff)
downloadclient-51a2ac628822e96459b3d570eada953ac8927d43.tar.gz
client-51a2ac628822e96459b3d570eada953ac8927d43.tar.bz2
client-51a2ac628822e96459b3d570eada953ac8927d43.zip
Added boostrap
Diffstat (limited to 'src/components')
-rw-r--r--src/components/App/App.module.scss56
-rw-r--r--src/components/App/App.tsx118
-rw-r--r--src/components/Content/Content.tsx74
-rw-r--r--src/components/Details/Details.tsx254
-rw-r--r--src/components/RequestSummary/RequestSummary.tsx49
5 files changed, 362 insertions, 189 deletions
diff --git a/src/components/App/App.module.scss b/src/components/App/App.module.scss
index 2036eb9..9837cdf 100644
--- a/src/components/App/App.module.scss
+++ b/src/components/App/App.module.scss
@@ -1,55 +1,3 @@
1.app { 1.content {
2 display: flex; 2 flex: 1 0 auto;
3 flex-flow: column nowrap;
4 grid-template-rows: auto 1fr;
5 height: 100vh;
6 overflow: hidden;
7}
8
9.main {
10 display: flex;
11 flex-flow: row nowrap;
12 flex-grow: 1;
13 overflow: hidden;
14}
15
16.header {
17 font-size: 1.2em;
18 display: flex;
19 flex-flow: row nowrap;
20 align-items: center;
21 justify-content: space-between;
22 background: black;
23 color: white;
24 padding: 1em;
25
26 a {
27 color: white;
28 }
29}
30
31.sidebar {
32 width: calc((6 / 16) * 100%);
33 height: 100%;
34 grid-area: sidebar;
35 border-right: 1px solid black;
36 overflow-y: auto;
37
38 li {
39 border-bottom: 1px solid gray;
40 }
41}
42
43.details {
44 width: calc((10 / 16) * 100%);
45 overflow: hidden;
46 height: 100%;
47}
48
49.noRequest, .noRequestSelected {
50 width: 100%;
51 height: 100%;
52 display: flex;
53 justify-content: center;
54 align-items: center;
55} 3}
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index e361aa5..61f88f6 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -6,6 +6,8 @@ import styles from './App.module.scss';
6import Details from "../Details/Details"; 6import Details from "../Details/Details";
7import RequestSummary from "../RequestSummary/RequestSummary"; 7import RequestSummary from "../RequestSummary/RequestSummary";
8import {getHost} from "../../utils"; 8import {getHost} from "../../utils";
9import {Container, Navbar, Col, Row, Nav, ListGroup} from "react-bootstrap";
10import classNames from "classnames";
9 11
10interface Config { 12interface Config {
11 url: string 13 url: string
@@ -50,29 +52,59 @@ export default function App() {
50 ), [selectedRequestIndex, calls]); 52 ), [selectedRequestIndex, calls]);
51 53
52 return config && ( 54 return config && (
53 <div className={styles.app}> 55 <>
54 <header className={styles.header}> 56 <Navbar
55 {statusMap[readyState]} TTUN 57 bg="dark"
56 <a href={config.url} target="_blank">{config.url}</a> 58 variant="dark"
57 </header> 59 expand
58 <main className={styles.main}> 60 >
59 <ul className={styles.sidebar}> 61 <Container fluid>
60 { 62 <Navbar.Brand>
61 calls.length > 0 63 {statusMap[readyState]} TTUN
62 ? calls.slice(0).reverse().map((requestResponse, index) => ( 64 </Navbar.Brand>
63 <li onClick={() => setSelectedRequestIndex(calls.length - index - 1)} key={`request-${index}`}> 65 <Navbar.Text>
64 <RequestSummary requestResponse={requestResponse} /> 66 <a href={config.url} target="_blank">{config.url}</a>
65 </li> 67 </Navbar.Text>
66 )) 68 </Container>
67 : ( 69 </Navbar>
68 <div className={styles.noRequest}> 70 <Container>
69 <p>No requests</p>
70 </div>
71 )
72 }
73 </ul>
74 71
75 <div className={styles.details}> 72 </Container>
73 <Row className={classNames(styles.content, 'gx-0', 'overflow-hidden')}>
74 <Col className='border-end overflow-auto' xs={4}>
75 <ListGroup variant='flush'>
76 {
77 calls.length > 0
78 ? (
79 calls.slice(0).reverse().map((requestResponse, index) => {
80 const selected = selectedRequestIndex === calls.length - index - 1;
81 return (
82 <ListGroup.Item
83 onClick={() => setSelectedRequestIndex(calls.length - index - 1)}
84 key={`request-${index}`}
85 className={classNames({
86 'bg-primary': selected,
87 'text-light': selected,
88 'border-bottom': true
89 })}
90 >
91 <RequestSummary
92 requestResponse={requestResponse}
93 selected={selected}
94 />
95 </ListGroup.Item>
96 )
97 })
98 )
99 : (
100 <div className={styles.noRequest}>
101 <p>No requests</p>
102 </div>
103 )
104 }
105 </ListGroup>
106 </Col>
107 <Col xs={8} className=''>
76 { 108 {
77 selectedRequest !== null 109 selectedRequest !== null
78 ? ( 110 ? (
@@ -83,8 +115,44 @@ export default function App() {
83 </div> 115 </div>
84 ) 116 )
85 } 117 }
86 </div> 118 </Col>
87 </main> 119 </Row>
88 </div> 120 </>
121 // <div className={styles.app}>
122 // <header className={styles.header}>
123 // {statusMap[readyState]} TTUN
124 // <a href={config.url} target="_blank">{config.url}</a>
125 // </header>
126 // <main className={styles.main}>
127 // <ul className={styles.sidebar}>
128 // {
129 // calls.length > 0
130 // ? calls.slice(0).reverse().map((requestResponse, index) => (
131 // <li onClick={() => setSelectedRequestIndex(calls.length - index - 1)} key={`request-${index}`}>
132 // <RequestSummary requestResponse={requestResponse} />
133 // </li>
134 // ))
135 // : (
136 // <div className={styles.noRequest}>
137 // <p>No requests</p>
138 // </div>
139 // )
140 // }
141 // </ul>
142 //
143 // <div className={styles.details}>
144 // {
145 // selectedRequest !== null
146 // ? (
147 // <Details requestResponse={selectedRequest} />
148 // ) : (
149 // <div className={styles.noRequestSelected}>
150 // <p>Select a request to inspect it</p>
151 // </div>
152 // )
153 // }
154 // </div>
155 // </main>
156 // </div>
89 ); 157 );
90} 158}
diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx
index a7b5949..cb5e6bc 100644
--- a/src/components/Content/Content.tsx
+++ b/src/components/Content/Content.tsx
@@ -13,6 +13,21 @@ import {
13} from "react"; 13} from "react";
14import ReactJson from 'react-json-view'; 14import ReactJson from 'react-json-view';
15import styles from './Content.module.scss'; 15import styles from './Content.module.scss';
16import {Col, Container, Row} from "react-bootstrap";
17
18function getHeader(headers: { [key: string]: string }, key: string, unit?: string): string | null {
19 console.log(headers, key)
20 try {
21 const [_, value] = Object
22 .entries(headers)
23 .find(([headerKey]) => headerKey.toLowerCase() === key.toLowerCase())
24 return unit !== undefined
25 ? `${value}${unit}`
26 : value
27 } catch {
28 return null;
29 }
30}
16 31
17interface ContentProps { 32interface ContentProps {
18 data: RequestPayload | ResponsePayload 33 data: RequestPayload | ResponsePayload
@@ -20,17 +35,29 @@ interface ContentProps {
20 raw?: boolean 35 raw?: boolean
21} 36}
22 37
23export default function Content({ raw, setRaw, ...props }: ContentProps): JSX.Element { 38export default function Content({ raw, setRaw, data }: ContentProps): JSX.Element {
24 return ( 39 return (
25 <div className={styles.content}> 40 <>
26 <div className={styles.header}> 41 <Container fluid className="border-bottom">
27 <input id='raw' type='checkbox' checked={raw} onChange={() => setRaw(!raw)}/> 42 <Row className="py-3">
28 <label htmlFor='raw'>Raw</label> 43 <Col>
29 </div> 44 <input id='raw' type='checkbox' checked={raw} onChange={() => setRaw(!raw)}/>
30 <div className={styles.body}> 45 <label htmlFor='raw' className="ps-1">Raw</label>
46 </Col>
47 <Col xs="auto">
48 {
49 [
50 getHeader(data.headers, 'content-length', 'bytes'),
51 getHeader(data.headers, 'content-type'),
52 ].filter(x => x !== null).join('; ')
53 }
54 </Col>
55 </Row>
56 </Container>
57 <Row>
31 {(() => { 58 {(() => {
32 try { 59 try {
33 return ContentBody({ ...props, raw }) 60 return ContentBody({ data, raw })
34 } catch { 61 } catch {
35 return ( 62 return (
36 <div className={styles.renderError}> 63 <div className={styles.renderError}>
@@ -40,8 +67,28 @@ export default function Content({ raw, setRaw, ...props }: ContentProps): JSX.El
40 ) 67 )
41 } 68 }
42 })()} 69 })()}
43 </div> 70 </Row>
44 </div> 71 </>
72 // <div className={styles.content}>
73 // <div className={styles.header}>
74 // <input id='raw' type='checkbox' checked={raw} onChange={() => setRaw(!raw)}/>
75 // <label htmlFor='raw'>Raw</label>
76 // </div>
77 // <div className={styles.body}>
78 // {(() => {
79 // try {
80 // return ContentBody({ ...props, raw })
81 // } catch {
82 // return (
83 // <div className={styles.renderError}>
84 // <p>Body could not be rendered</p>
85 // <a onClick={() => setRaw(true)}>View raw</a>
86 // </div>
87 // )
88 // }
89 // })()}
90 // </div>
91 // </div>
45 ) 92 )
46}; 93};
47 94
@@ -51,12 +98,7 @@ function ContentBody({ data, raw = false }: Omit<ContentProps, 'setRaw'>) {
51 return ''; 98 return '';
52 } 99 }
53 100
54 const [_, type] = ( 101 const type = getHeader(data.headers, 'content-type');
55 Object
56 .entries(data.headers)
57 .find(([key]) => key.toLowerCase() === 'content-type')
58 );
59
60 return type.toLowerCase().split(';')[0]; 102 return type.toLowerCase().split(';')[0];
61 }, [data, raw]); 103 }, [data, raw]);
62 104
diff --git a/src/components/Details/Details.tsx b/src/components/Details/Details.tsx
index e91c98c..c96aa7b 100644
--- a/src/components/Details/Details.tsx
+++ b/src/components/Details/Details.tsx
@@ -10,6 +10,16 @@ import RequestSummary from "../RequestSummary/RequestSummary";
10import classNames from 'classnames'; 10import classNames from 'classnames';
11import Content from "../Content/Content"; 11import Content from "../Content/Content";
12import {getHost} from "../../utils"; 12import {getHost} from "../../utils";
13import {
14 Button,
15 Card,
16 Col,
17 Container,
18 Nav,
19 NavItem,
20 Row,
21 Table
22} from "react-bootstrap";
13 23
14 24
15interface TimingProps { 25interface TimingProps {
@@ -39,17 +49,31 @@ interface HeadersProps {
39 49
40function Headers({ title, headers }: HeadersProps) { 50function Headers({ title, headers }: HeadersProps) {
41 return ( 51 return (
42 <div className={styles.headers}> 52 <Card className="m-3">
43 <h2>{ title }</h2> 53 <Table
44 { 54 striped
45 Object.entries(headers).map(([key, value]) => ( 55 responsive
46 <> 56 borderless
47 <div>{key}</div> 57 hover
48 <div>{value}</div> 58 className='mb-0'
49 </> 59 >
50 )) 60 <thead>
51 } 61 <tr>
52 </div> 62 <th colSpan={2} className="bg-dark text-white rounded-top">{ title }</th>
63 </tr>
64 </thead>
65 <tbody>
66 {
67 Object.entries(headers).map(([key, value]) => (
68 <tr>
69 <td>{key}</td>
70 <td>{value}</td>
71 </tr>
72 ))
73 }
74 </tbody>
75 </Table>
76 </Card>
53 ) 77 )
54} 78}
55 79
@@ -75,73 +99,147 @@ export default function Details({ requestResponse }: DetailsProps) {
75 ), [requestResponse]); 99 ), [requestResponse]);
76 100
77 return ( 101 return (
78 <div className={styles.details}> 102 <>
79 <div className={styles.header}> 103 <Row>
80 <RequestSummary requestResponse={requestResponse} className={styles.summary}/> 104 <Col>
81 105 <Container fluid style={{ fontSize: '1.5em' }} className="py-3">
82 <div className={styles.tabs}> 106 <RequestSummary requestResponse={requestResponse} className={styles.summary}/>
83 <button 107 </Container>
84 onClick={() => selectTab('headers')} 108 </Col>
85 className={classNames(styles.tab, { 109 </Row>
86 [styles.selected]: tab === 'headers' 110 <Row className="gx-0 d-flex">
87 })} 111 <Col>
88 > 112 <Nav
89 Headers 113 variant="tabs"
90 </button> 114 activeKey={tab}
91 <button 115 onSelect={(tab) => selectTab(tab as Tab)}
92 onClick={() => selectTab('request')}
93 className={classNames(styles.tab, {
94 [styles.selected]: tab === 'request'
95 })}
96 > 116 >
97 Request 117 <Nav.Item>
98 </button> 118 <Nav.Link eventKey="headers">
99 <button 119 Headers
100 onClick={() => selectTab('response')} 120 </Nav.Link >
101 className={classNames(styles.tab, { 121 </Nav.Item>
102 [styles.selected]: tab === 'response' 122 <Nav.Item>
103 })} 123 <Nav.Link eventKey="request">
104 disabled={requestResponse.response === undefined} 124 Request
105 > 125 </Nav.Link>
106 Response 126 </Nav.Item>
107 </button> 127 <Nav.Item>
108 128 <Nav.Link
109 <div className={styles.emptySpace}> 129 eventKey="response"
110 <Timing timing={requestResponse.response?.timing ?? NaN} /> 130 disabled={requestResponse.response === undefined}
111 <button className={styles.resend} onClick={() => resend()}>Resend</button> 131 >
112 </div> 132 Response
113 </div> 133 </Nav.Link>
114 </div> 134 </Nav.Item>
115 <div className={styles.content}> 135 </Nav>
116 { 136 </Col>
117 tab === 'headers' && ( 137 <Col className="border-bottom px-3 " xs="auto">
118 <> 138 <Timing timing={requestResponse.response?.timing ?? NaN} />
119 <Headers 139 <Button variant="outline-primary" onClick={() => resend()} className="ms-3">
120 title="Request Headers" 140 Resend
121 headers={requestResponse.request.headers} 141 </Button>
122 /> 142 </Col>
123 { 143 </Row>
124 requestResponse.response && ( 144 <Row>
125 <Headers 145 <Col>
126 title="Response Headers" 146 {
127 headers={requestResponse.response.headers} 147 tab === 'headers' && (
128 /> 148 <>
129 ) 149 <Headers
130 } 150 title="Request Headers"
131 </> 151 headers={requestResponse.request.headers}
132 ) 152 />
133 } 153 {
134 { 154 requestResponse.response && (
135 tab === 'request' && ( 155 <Headers
136 <Content data={requestResponse.request} raw={raw} setRaw={setRaw}/> 156 title="Response Headers"
137 ) 157 headers={requestResponse.response.headers}
138 } 158 />
139 { 159 )
140 tab === 'response' && requestResponse.response !== undefined && ( 160 }
141 <Content data={requestResponse.response} raw={raw} setRaw={setRaw}/> 161 </>
142 ) 162 )
143 } 163 }
144 </div> 164 {
145 </div> 165 tab === 'request' && (
166 <Content data={requestResponse.request} raw={raw} setRaw={setRaw}/>
167 )
168 }
169 {
170 tab === 'response' && requestResponse.response !== undefined && (
171 <Content data={requestResponse.response} raw={raw} setRaw={setRaw}/>
172 )
173 }
174 </Col>
175 </Row>
176 </>
177 // <div className={styles.details}>
178 // <div className={styles.header}>
179 //
180 // <div className={styles.tabs}>
181 // <button
182 // onClick={() => selectTab('headers')}
183 // className={classNames(styles.tab, {
184 // [styles.selected]: tab === 'headers'
185 // })}
186 // >
187 // Headers
188 // </button>
189 // <button
190 // onClick={() => selectTab('request')}
191 // className={classNames(styles.tab, {
192 // [styles.selected]: tab === 'request'
193 // })}
194 // >
195 // Request
196 // </button>
197 // <button
198 // onClick={() => selectTab('response')}
199 // className={classNames(styles.tab, {
200 // [styles.selected]: tab === 'response'
201 // })}
202 // disabled={requestResponse.response === undefined}
203 // >
204 // Response
205 // </button>
206 //
207 // <div className={styles.emptySpace}>
208 // <Timing timing={requestResponse.response?.timing ?? NaN} />
209 // <button className={styles.resend} onClick={() => resend()}>Resend</button>
210 // </div>
211 // </div>
212 // </div>
213 // <div className={styles.content}>
214 // {
215 // tab === 'headers' && (
216 // <>
217 // <Headers
218 // title="Request Headers"
219 // headers={requestResponse.request.headers}
220 // />
221 // {
222 // requestResponse.response && (
223 // <Headers
224 // title="Response Headers"
225 // headers={requestResponse.response.headers}
226 // />
227 // )
228 // }
229 // </>
230 // )
231 // }
232 // {
233 // tab === 'request' && (
234 // <Content data={requestResponse.request} raw={raw} setRaw={setRaw}/>
235 // )
236 // }
237 // {
238 // tab === 'response' && requestResponse.response !== undefined && (
239 // <Content data={requestResponse.response} raw={raw} setRaw={setRaw}/>
240 // )
241 // }
242 // </div>
243 // </div>
146 ) 244 )
147} 245}
diff --git a/src/components/RequestSummary/RequestSummary.tsx b/src/components/RequestSummary/RequestSummary.tsx
index af928c0..ea28f3e 100644
--- a/src/components/RequestSummary/RequestSummary.tsx
+++ b/src/components/RequestSummary/RequestSummary.tsx
@@ -3,31 +3,48 @@ import * as React from "react";
3import classNames from "classnames"; 3import classNames from "classnames";
4 4
5import styles from './RequestSummary.module.scss'; 5import styles from './RequestSummary.module.scss';
6import {Badge, Card, Col, Row} from "react-bootstrap";
6 7
7interface RequestSummaryProps { 8interface RequestSummaryProps {
9 selected?: boolean
8 requestResponse: RequestResponse 10 requestResponse: RequestResponse
9 className?: string
10} 11}
11 12
12function isBetween(value: number, min: number, max: number) { 13function isBetween(value: number, min: number, max: number) {
13 return value >= min && value <= max 14 return value >= min && value <= max
14} 15}
15 16
16export default function RequestSummary({ requestResponse: { request, response }, className = ''}: RequestSummaryProps) { 17function calcBadgeVariant(statusCode: number | undefined): string {
17 const statusCode = response?.status ?? 0 18 if (statusCode === undefined) {
19 return 'secondary';
20 } else if (isBetween(statusCode, 100, 199)) {
21 return 'info';
22 } else if (isBetween(statusCode, 200, 299)) {
23 return 'success';
24 } else if (isBetween(statusCode, 300, 399)) {
25 return 'primary';
26 } else if (isBetween(statusCode, 400, 499)) {
27 return 'danger';
28 } else if (isBetween(statusCode, 500, 599)) {
29 return 'warning';
30 }
31}
32
33export default function RequestSummary({ requestResponse: { request, response }, selected = false }: RequestSummaryProps) {
18 return ( 34 return (
19 <div className={classNames(styles.requestSummary, className)}> 35 <Row>
20 <span className={styles.method}>{ request.method }</span> 36 <Col className='flex-grow-0 d-flex align-items-center'>{request.method}</Col>
21 <p>{ request.path }</p> 37 <Col className='flex-grow-1'>{ request.path }</Col>
22 <span className={classNames(styles.statusCode, { 38 <Col className='flex-grow-0 d-flex align-items-center'>
23 [styles.info]: isBetween(statusCode, 100, 199), 39 <Badge
24 [styles.success]: isBetween(statusCode, 200, 299), 40 className={classNames({
25 [styles.redirect]: isBetween(statusCode, 300, 399), 41 'border': selected,
26 [styles.clientError]: isBetween(statusCode, 400, 499), 42 })}
27 [styles.serverError]: isBetween(statusCode, 500, 599) 43 bg={calcBadgeVariant(response?.status)}
28 })}> 44 >
29 { response?.status ?? 'Loading...'} 45 { response?.status ?? 'Loading...'}
30 </span> 46 </Badge>
31 </div> 47 </Col>
48 </Row>
32 ) 49 )
33} 50}