diff --git a/src/dashboard/Data/Config/Config.react.js b/src/dashboard/Data/Config/Config.react.js index 2cf56d3691..273b0b8dc2 100644 --- a/src/dashboard/Data/Config/Config.react.js +++ b/src/dashboard/Data/Config/Config.react.js @@ -30,7 +30,8 @@ class Config extends TableView { modalOpen: false, modalParam: '', modalType: 'String', - modalValue: '' + modalValue: '', + modalMasterKeyOnly: false }; } @@ -58,13 +59,16 @@ class Config extends TableView { if (!this.state.modalOpen) { return null; } + const { currentApp = {} } = this.context; return ( this.setState({ modalOpen: false })} param={this.state.modalParam} type={this.state.modalType} - value={this.state.modalValue} /> + value={this.state.modalValue} + masterKeyOnly={this.state.modalMasterKeyOnly} + parseServerVersion={currentApp.serverInfo && currentApp.serverInfo.parseServerVersion} /> ); } @@ -103,9 +107,11 @@ class Config extends TableView { modalOpen: true, modalParam: data.param, modalType: type, - modalValue: modalValue + modalValue: modalValue, + modalMasterKeyOnly: data.masterKeyOnly }); - let columnStyle = { width: '30%', cursor: 'pointer' }; + let columnStyleLarge = { width: '30%', cursor: 'pointer' }; + let columnStyleSmall = { width: '15%', cursor: 'pointer' }; let openModalValueColumn = () => { if (data.value instanceof Parse.File) { @@ -116,9 +122,10 @@ class Config extends TableView { return ( - {data.param} - {type} - {value} + {data.param} + {type} + {value} + {data.masterKeyOnly.toString()} @@ -131,8 +138,9 @@ class Config extends TableView { renderHeaders() { return [ Parameter, - Type, - Value + Type, + Value, + Master key only ]; } @@ -151,9 +159,11 @@ class Config extends TableView { let data = undefined; if (this.props.config.data) { let params = this.props.config.data.get('params'); + let masterKeyOnlyParams = this.props.config.data.get('masterKeyOnly') || {}; if (params) { data = []; params.forEach((value, param) => { + let masterKeyOnly = masterKeyOnlyParams.get(param) || false; let type = typeof value; if (type === 'object' && value.__type == 'File') { value = Parse.File.fromJSON(value); @@ -161,10 +171,8 @@ class Config extends TableView { else if (type === 'object' && value.__type == 'GeoPoint') { value = new Parse.GeoPoint(value); } - - data.push({ param: param, value: value }) + data.push({ param: param, value: value, masterKeyOnly: masterKeyOnly }) }); - data.sort((object1, object2) => { return object1.param.localeCompare(object2.param); }); @@ -173,10 +181,10 @@ class Config extends TableView { return data; } - saveParam({ name, value }) { + saveParam({ name, value, masterKeyOnly }) { this.props.config.dispatch( ActionTypes.SET, - { param: name, value: value } + { param: name, value: value, masterKeyOnly: masterKeyOnly } ).then(() => { this.setState({ modalOpen: false }); }, () => { @@ -196,7 +204,8 @@ class Config extends TableView { modalOpen: true, modalParam: '', modalType: 'String', - modalValue: '' + modalValue: '', + modalMasterKeyOnly: false }); } } diff --git a/src/dashboard/Data/Config/ConfigDialog.react.js b/src/dashboard/Data/Config/ConfigDialog.react.js index 75d73d147d..5994fea1b8 100644 --- a/src/dashboard/Data/Config/ConfigDialog.react.js +++ b/src/dashboard/Data/Config/ConfigDialog.react.js @@ -18,6 +18,8 @@ import React from 'react'; import TextInput from 'components/TextInput/TextInput.react'; import Toggle from 'components/Toggle/Toggle.react'; import validateNumeric from 'lib/validateNumeric'; +import styles from 'dashboard/Data/Browser/Browser.scss'; +import semver from 'semver'; const PARAM_TYPES = [ 'Boolean', @@ -108,13 +110,15 @@ export default class ConfigDialog extends React.Component { this.state = { value: null, type: 'String', - name: '' + name: '', + masterKeyOnly: false }; if (props.param.length > 0) { this.state = { name: props.param, type: props.type, value: props.value, + masterKeyOnly: props.masterKeyOnly }; } } @@ -176,6 +180,7 @@ export default class ConfigDialog extends React.Component { this.props.onConfirm({ name: this.state.name, value: GET_VALUE[this.state.type](this.state.value), + masterKeyOnly: this.state.masterKeyOnly }); } @@ -228,6 +233,30 @@ export default class ConfigDialog extends React.Component { description='Use this to configure your app. You can change it at any time.' /> } input={EDITORS[this.state.type](this.state.value, (value) => { this.setState({ value }) })} /> + + { + /* + Add `Requires master key` field if parse-server version >= 3.8.0, + that is the minimum version that supports this feature. + */ + semver.valid(this.props.parseServerVersion) && semver.gte(this.props.parseServerVersion, '3.8.0') + ? + } + input={ + this.setState({ masterKeyOnly })} + additionalStyles={{ margin: '0px' }} /> + } + className={styles.addColumnToggleWrapper} + /> + : null + } ); } diff --git a/src/lib/stores/ConfigStore.js b/src/lib/stores/ConfigStore.js index 340e7f0ea4..d30907b800 100644 --- a/src/lib/stores/ConfigStore.js +++ b/src/lib/stores/ConfigStore.js @@ -16,6 +16,7 @@ export const ActionTypes = keyMirror(['FETCH', 'SET', 'DELETE']); // Config state should be an Immutable Map with the following fields: // - lastFetch: the last time all data was fetched from the server // - params: An Immutable Map of parameter strings to values +// - masterKeyOnly: An Immutable Map of parameter properties for read with master key only function ConfigStore(state, action) { action.app.setParseKeys(); @@ -27,22 +28,24 @@ function ConfigStore(state, action) { {}, { useMasterKey: true } ).then((result) => { - return Map({ lastFetch: new Date(), params: Map(result.params) }); + return Map({ lastFetch: new Date(), params: Map(result.params), masterKeyOnly: Map(result.masterKeyOnly) }); }); case ActionTypes.SET: return Parse._request( 'PUT', 'config', - { params: { [action.param]: Parse._encode(action.value) } }, + { params: { [action.param]: Parse._encode(action.value) }, masterKeyOnly: { [action.param]: action.masterKeyOnly} }, { useMasterKey: true } ).then(() => { - return state.setIn(['params', action.param], action.value); + return state + .setIn(['params', action.param], action.value) + .setIn(['masterKeyOnly', action.param], action.masterKeyOnly); }); case ActionTypes.DELETE: return Parse._request( 'PUT', 'config', - { params: { [action.param]: { __op: 'Delete' } } }, + { params: { [action.param]: { __op: 'Delete' } }, masterKeyOnly: { [action.param]: { __op: 'Delete' } } }, { useMasterKey: true } ).then(() => { return state.deleteIn(['params', action.param]);