diff --git a/README.md b/README.md index 0a12d09e8c..e39438399a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Other Configuration Options](#other-configuration-options) - [Prevent columns sorting](#prevent-columns-sorting) - [Custom order in the filter popup](#custom-order-in-the-filter-popup) + - [Scripts](#scripts) - [Running as Express Middleware](#running-as-express-middleware) - [Deploying Parse Dashboard](#deploying-parse-dashboard) - [Preparing for Deployment](#preparing-for-deployment) @@ -362,6 +363,71 @@ For example: You can conveniently create a filter definition without having to write it by hand by first saving a filter in the data browser, then exporting the filter definition under *App Settings > Export Class Preferences*. +### Scripts + +You can specify scripts to execute Cloud Functions with the `scripts` option: + + +```json +"apps": [ + { + "scripts": [ + { + "title": "Delete Account", + "classes": ["_User"], + "cloudCodeFunction": "deleteAccount" + } + ] + } +] +``` + +Next, define the Cloud Function in Parse Server that will be called. The object that has been selected in the data browser will be made available as a request parameter: + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}, { + requireMaster: true +}); +``` + +⚠️ Depending on your Parse Server version you may need to set the Parse Server option `encodeParseObjectInCloudFunction` to `true` so that the selected object in the data browser is made available in the Cloud Function as an instance of `Parse.Object`. If the option is not set, is set to `false`, or you are using an older version of Parse Server, the object is made available as a plain JavaScript object and needs to be converted from a JSON object to a `Parse.Object` instance with `req.params.object = Parse.Object.fromJSON(req.params.object);`, before you can call any `Parse.Object` properties and methods on it. + +For older versions of Parse Server: + +
+Parse Server >=4.4.0 <6.2.0 + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + req.params.object = Parse.Object.fromJSON(req.params.object); + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}, { + requireMaster: true +}); +``` + +
+ +
+Parse Server >=2.1.4 <4.4.0 + +```js +Parse.Cloud.define('deleteAccount', async (req) => { + if (!req.master || !req.params.object) { + throw 'Unauthorized'; + } + req.params.object = Parse.Object.fromJSON(req.params.object); + req.params.object.set('deleted', true); + await req.params.object.save(null, {useMasterKey: true}); +}); +``` + +
+ # Running as Express Middleware Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware. diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 94d5cfc2b8..11fb0401f8 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -15,7 +15,6 @@ import React, { Component } from 'react'; import styles from 'components/BrowserCell/BrowserCell.scss'; import baseStyles from 'stylesheets/base.scss'; import * as ColumnPreferences from 'lib/ColumnPreferences'; - export default class BrowserCell extends Component { constructor() { super(); @@ -279,6 +278,29 @@ export default class BrowserCell extends Component { }); } + const validScripts = (this.props.scripts || []).filter(script => script.classes?.includes(this.props.className)); + if (validScripts.length) { + onEditSelectedRow && contextMenuOptions.push({ + text: 'Scripts', + items: validScripts.map(script => { + return { + text: script.title, + callback: async () => { + try { + const object = Parse.Object.extend(this.props.className).createWithoutData(this.props.objectId); + const response = await Parse.Cloud.run(script.cloudCodeFunction, {object: object.toPointer()}, {useMasterKey: true}); + this.props.showNote(response || `${script.title} ran with object ${object.id}}`); + this.props.onRefresh(); + } catch (e) { + this.props.showNote(e.message, true); + console.log(`Could not run ${script.title}: ${e}`); + } + } + } + }) + }); + } + return contextMenuOptions; } diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 5ae4e78ac3..7a6c99a74e 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -100,7 +100,11 @@ export default class BrowserRow extends Component { markRequiredFieldRow={markRequiredFieldRow} setCopyableValue={setCopyableValue} setContextMenu={setContextMenu} - onEditSelectedRow={onEditSelectedRow} /> + onEditSelectedRow={onEditSelectedRow} + showNote={this.props.showNote} + onRefresh={this.props.onRefresh} + scripts={this.props.scripts} + /> ); })} diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 235241ccd8..2fb8ec22aa 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -159,6 +159,9 @@ export default class BrowserTable extends React.Component { setContextMenu={this.props.setContextMenu} onEditSelectedRow={this.props.onEditSelectedRow} markRequiredFieldRow={this.props.markRequiredFieldRow} + showNote={this.props.showNote} + onRefresh={this.props.onRefresh} + scripts={this.context.scripts} />