|
5 | 5 | * This source code is licensed under the license found in the LICENSE file in
|
6 | 6 | * the root directory of this source tree.
|
7 | 7 | */
|
8 |
| -import React from 'react' |
9 |
| -import CategoryList from 'components/CategoryList/CategoryList.react' |
10 |
| -import DashboardView from 'dashboard/DashboardView.react' |
| 8 | +import PropTypes from 'lib/PropTypes'; |
| 9 | +import Button from 'components/Button/Button.react'; |
| 10 | +import DashboardView from 'dashboard/DashboardView.react'; |
| 11 | +import Dropdown from 'components/Dropdown/Dropdown.react'; |
| 12 | +import Field from 'components/Field/Field.react'; |
| 13 | +import Fieldset from 'components/Fieldset/Fieldset.react'; |
| 14 | +import fieldStyle from 'components/Field/Field.scss'; |
| 15 | +import FlowFooter from 'components/FlowFooter/FlowFooter.react'; |
| 16 | +import FormNote from 'components/FormNote/FormNote.react'; |
| 17 | +import generateCurl from 'dashboard/Data/ApiConsole/generateCurl'; |
| 18 | +import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react'; |
| 19 | +import Label from 'components/Label/Label.react'; |
| 20 | +import Modal from 'components/Modal/Modal.react'; |
| 21 | +import Option from 'components/Dropdown/Option.react'; |
| 22 | +import Parse from 'parse'; |
| 23 | +import ParseApp from 'lib/ParseApp'; |
| 24 | +import React from 'react'; |
| 25 | +import request from 'dashboard/Data/ApiConsole/request'; |
| 26 | +import styles from 'dashboard/Data/ApiConsole/ApiConsole.scss'; |
| 27 | +import TextInput from 'components/TextInput/TextInput.react'; |
| 28 | +import Toggle from 'components/Toggle/Toggle.react'; |
| 29 | +import Toolbar from 'components/Toolbar/Toolbar.react'; |
11 | 30 |
|
12 | 31 | export default class ApiConsole extends DashboardView {
|
13 | 32 |
|
14 | 33 | constructor() {
|
15 | 34 | super();
|
16 | 35 | this.section = 'Core';
|
17 | 36 | this.subsection = 'API Console';
|
| 37 | + |
| 38 | + this.state = { |
| 39 | + method: 'GET', |
| 40 | + endpoint: '', |
| 41 | + useMasterKey: false, |
| 42 | + runAsIdentifier: '', |
| 43 | + sessionToken: null, |
| 44 | + parameters: '', |
| 45 | + response: {results:[]}, |
| 46 | + fetchingUser: false, |
| 47 | + inProgress: false, |
| 48 | + error: false, |
| 49 | + curlModal: false, |
| 50 | + }; |
18 | 51 | }
|
19 | 52 |
|
20 |
| - renderSidebar() { |
21 |
| - const { path } = this.props.match |
22 |
| - const current = path.substr(path.lastIndexOf('/') + 1, path.length - 1) |
23 |
| - return ( |
24 |
| - <CategoryList current={current} linkPrefix={'api_console/'} categories={[ |
25 |
| - { name: 'REST Console', id: 'rest' }, |
26 |
| - { name: 'GraphQL Console', id: 'graphql' } |
27 |
| - ]} /> |
28 |
| - ) |
| 53 | + fetchUser() { |
| 54 | + if (this.state.runAsIdentifier.length === 0) { |
| 55 | + this.setState({ error: false, sessionToken: null }); |
| 56 | + return; |
| 57 | + } |
| 58 | + Parse.Query.or( |
| 59 | + new Parse.Query(Parse.User).equalTo('username', this.state.runAsIdentifier ), |
| 60 | + new Parse.Query(Parse.User).equalTo('objectId', this.state.runAsIdentifier ) |
| 61 | + ).first({ useMasterKey: true }).then((found) => { |
| 62 | + if (found) { |
| 63 | + if (found.getSessionToken()) { |
| 64 | + this.setState({ sessionToken: found.getSessionToken(), error: false, fetchingUser: false }); |
| 65 | + } else { |
| 66 | + // Check the Sessions table |
| 67 | + new Parse.Query(Parse.Session).equalTo('user', found).first({ useMasterKey: true }).then((session) => { |
| 68 | + if (session) { |
| 69 | + this.setState({ sessionToken: session.getSessionToken(), error: false, fetchingUser: false }); |
| 70 | + } else { |
| 71 | + this.setState({ error: 'Unable to find any active sessions for that user.', fetchingUser: false }); |
| 72 | + } |
| 73 | + }, () => { |
| 74 | + this.setState({ error: 'Unable to find any active sessions for that user.', fetchingUser: false }); |
| 75 | + }); |
| 76 | + } |
| 77 | + } else { |
| 78 | + this.setState({ error: 'Unable to find that user.', fetchingUser: false }); |
| 79 | + } |
| 80 | + }, () => { |
| 81 | + this.setState({ error: 'Unable to find that user.', fetchingUser: false }); |
| 82 | + }); |
| 83 | + this.setState({ fetchingUser: true }); |
| 84 | + } |
| 85 | + |
| 86 | + makeRequest() { |
| 87 | + let endpoint = this.state.endpoint + (this.state.method === 'GET' ? `?${this.state.parameters}` : ''); |
| 88 | + let payload = (this.state.method === 'DELETE' || this.state.method === 'GET') ? null : this.state.parameters; |
| 89 | + let options = {}; |
| 90 | + if (this.state.useMasterKey) { |
| 91 | + options.useMasterKey = true; |
| 92 | + } |
| 93 | + if (this.state.sessionToken) { |
| 94 | + options.sessionToken = this.state.sessionToken; |
| 95 | + } |
| 96 | + request( |
| 97 | + this.context.currentApp, |
| 98 | + this.state.method, |
| 99 | + endpoint, |
| 100 | + payload, |
| 101 | + options |
| 102 | + ).then((response) => { |
| 103 | + this.setState({ response }); |
| 104 | + document.body.scrollTop = 540; |
| 105 | + }); |
| 106 | + } |
| 107 | + |
| 108 | + showCurl() { |
| 109 | + this.setState({ curlModal: true }); |
29 | 110 | }
|
30 | 111 |
|
31 | 112 | renderContent() {
|
32 |
| - const child = React.Children.only(this.props.children); |
33 |
| - return React.cloneElement( |
34 |
| - child, |
35 |
| - { ...child.props } |
36 |
| - ) |
| 113 | + const methodDropdown = |
| 114 | + <Dropdown onChange={(method) => this.setState({method})} value={this.state.method}> |
| 115 | + <Option value='GET'>GET</Option> |
| 116 | + <Option value='POST'>POST</Option> |
| 117 | + <Option value='PUT'>PUT</Option> |
| 118 | + <Option value='DELETE'>DELETE</Option> |
| 119 | + </Dropdown> |
| 120 | + |
| 121 | + let hasError = this.state.fetchingUser || |
| 122 | + this.state.endpoint.length === 0 || |
| 123 | + (this.state.runAsIdentifier.length > 0 && !this.state.sessionToken); |
| 124 | + let parameterPlaceholder = 'where={"username":"johndoe"}'; |
| 125 | + if (this.state.method === 'POST' || this.state.method === 'PUT') { |
| 126 | + parameterPlaceholder = '{"name":"John"}'; |
| 127 | + } |
| 128 | + |
| 129 | + let modal = null; |
| 130 | + if (this.state.curlModal) { |
| 131 | + let payload = this.state.method === 'DELETE' ? null : this.state.parameters; |
| 132 | + let options = {}; |
| 133 | + if (this.state.useMasterKey) { |
| 134 | + options.useMasterKey = true; |
| 135 | + } |
| 136 | + if (this.state.sessionToken) { |
| 137 | + options.sessionToken = this.state.sessionToken; |
| 138 | + } |
| 139 | + let content = generateCurl( |
| 140 | + this.context.currentApp, |
| 141 | + this.state.method, |
| 142 | + this.state.endpoint, |
| 143 | + payload, |
| 144 | + options |
| 145 | + ); |
| 146 | + modal = ( |
| 147 | + <Modal |
| 148 | + title='cURL Request' |
| 149 | + subtitle='Use this to replicate the request' |
| 150 | + icon='laptop-outline' |
| 151 | + customFooter={ |
| 152 | + <div className={styles.footer}> |
| 153 | + <Button primary={true} value='Close' onClick={() => this.setState({ curlModal: false })} /> |
| 154 | + </div> |
| 155 | + }> |
| 156 | + <div className={styles.curl}>{content}</div> |
| 157 | + </Modal> |
| 158 | + ); |
| 159 | + } |
| 160 | + |
| 161 | + return ( |
| 162 | + <div style={{ padding: '120px 0 60px 0' }}> |
| 163 | + <Fieldset |
| 164 | + legend='Send a test query' |
| 165 | + description='Try out some queries, and take a look at what they return.'> |
| 166 | + <Field |
| 167 | + label={<Label text='What type of request?' />} |
| 168 | + input={methodDropdown} /> |
| 169 | + <Field |
| 170 | + label={<Label text='Which endpoint?' description={<span>Not sure what endpoint you need?<br />Take a look at our <a href="http://docs.parseplatform.org/rest/guide/">REST API guide</a>.</span>} />} |
| 171 | + input={<TextInput value={this.state.endpoint} monospace={true} placeholder={'classes/_User'} onChange={(endpoint) => this.setState({endpoint})} />} /> |
| 172 | + <Field |
| 173 | + label={<Label text='Use Master Key?' description={'This will bypass any ACL/CLPs.'} />} |
| 174 | + input={<Toggle value={this.state.useMasterKey} onChange={(useMasterKey) => this.setState({ useMasterKey })} />} /> |
| 175 | + <Field |
| 176 | + label={<Label text='Run as...' description={'Send your query as a specific user. You can use their username or Object ID.'} />} |
| 177 | + input={<TextInput value={this.state.runAsIdentifier} monospace={true} placeholder={'Username or ID'} onChange={(runAsIdentifier) => this.setState({runAsIdentifier})} onBlur={this.fetchUser.bind(this)} />} /> |
| 178 | + <FormNote color='red' show={!!this.state.error}>{this.state.error}</FormNote> |
| 179 | + <Field |
| 180 | + label={<Label text='Query parameters' description={<span>Learn more about query parameters in our <a href="http://docs.parseplatform.org/rest/guide/#queries">REST API guide</a>.</span>} />} |
| 181 | + input={<TextInput value={this.state.parameters} monospace={true} multiline={true} placeholder={parameterPlaceholder} onChange={(parameters) => this.setState({parameters})} />} /> |
| 182 | + </Fieldset> |
| 183 | + <Fieldset |
| 184 | + legend='Results' |
| 185 | + description=''> |
| 186 | + <div className={fieldStyle.field}> |
| 187 | + <JsonPrinter object={this.state.response} /> |
| 188 | + </div> |
| 189 | + </Fieldset> |
| 190 | + <Toolbar section='Core' subsection='API Console' /> |
| 191 | + <FlowFooter |
| 192 | + primary={<Button primary={true} disabled={hasError} value='Send Query' progress={this.state.inProgress} onClick={this.makeRequest.bind(this)} />} |
| 193 | + secondary={<Button disabled={hasError} value='Export to cURL' onClick={this.showCurl.bind(this)} />} /> |
| 194 | + {modal} |
| 195 | + </div> |
| 196 | + ); |
37 | 197 | }
|
38 | 198 | }
|
| 199 | + |
| 200 | +ApiConsole.contextTypes = { |
| 201 | + currentApp: PropTypes.instanceOf(ParseApp) |
| 202 | +}; |
0 commit comments