From 957bfc195e748494cd4bafa133669b6d84ad9769 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Wed, 16 Mar 2016 17:04:04 -0700 Subject: [PATCH 1/3] Improve getting started experience for Parse Dashboard --- .npmignore | 1 + CONTRIBUTING.md | 6 +- Parse-Dashboard/index.js | 146 +++++++++++++++++++++++++-------------- README.md | 69 +++++++----------- package.json | 12 +++- 5 files changed, 131 insertions(+), 103 deletions(-) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..741ee9aefb --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +Parse Dashboard/parse-dashboard-config.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf5443c30c..eae02478b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,9 @@ We want to make contributing to this project as easy and transparent as possible. ## Our Development Process -The standard installation method also clones the git repository, so you can start making and submitting changes right away. +Get started by cloning this repository and and running `npm install` inside it. Create a file called `parse-dashboad-config.json` in the Parse-Dashboard folder inside the repo, using the format described in the readme. -When working on the dashboard, use `npm run dashboard` and visit `localhost:4040` to see your dashboard. The `npm` script will automatically re-build your files when you change them, so after making a change, all you need to do is refresh the page. +When working on the dashboard, use `npm run dashboard` and visit `localhost:4040` to see your dashboard. The `npm run dashboard` script will automatically re-build your files when you change them, so after making a change, all you need to do is refresh the page. When working on React components, use `npm run pig` and visit `localhost:4041` to view our component library and documentation (you can have both Dashboard and PIG running at once). The demos for each component are the primary way we test components, although there are also a small number of automated tests you can run with `npm test`. If you would like to create a new component that does not exist in the component library, use `npm run generate yourComponentName` to generate boilerplate code and quickly get started. @@ -33,8 +33,6 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. - ## License By contributing to Parse Dashboard, you agree that your contributions will be licensed under its license. - diff --git a/Parse-Dashboard/index.js b/Parse-Dashboard/index.js index c7c237b7d6..96e0cc0348 100644 --- a/Parse-Dashboard/index.js +++ b/Parse-Dashboard/index.js @@ -6,31 +6,25 @@ * the root directory of this source tree. */ // Command line tool for npm start +"use strict" +const packageJson = require('package-json'); +const basicAuth = require('basic-auth'); +const path = require('path'); +const jsonFile = require('json-file-plus'); +const express = require('express'); -var DEFAULT_DASHBOARD_CONFIG = __dirname + '/parse-dashboard-config.json'; - -var program = require("commander"); -program.option('--port [port]', "the port to run parse-dashboard"); -program.option('--config [config]', "the path to the configuration file"); -program.option('--allowInsecureHTTP [allowInsecureHTTP]', 'set that flag when parse server is behind an HTTPS load balancer/proxy'); +const program = require('commander'); +program.option('--appId [appId]', 'the app Id of the app you would like to manage.'); +program.option('--masterKey [masterKey]', 'the master key of the app you would like to manage.'); +program.option('--serverURL [serverURL]', 'the server url of the app you would like to manage.'); +program.option('--appName [appName]', 'the name of the app you would like to manage. Optional.'); +program.option('--config [config]', 'the path to the configuration file'); +program.option('--port [port]', 'the port to run parse-dashboard'); +program.option('--allowInsecureHTTP [allowInsecureHTTP]', 'set this flag when you are running the dashboard behind an HTTPS load balancer or proxy with early SSL termination.'); program.parse(process.argv); -// collect the variables -var configFile = program.config || DEFAULT_DASHBOARD_CONFIG; -var port = program.port || process.env.PORT; -var allowInsecureHTTP = program.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP; - -var packageJson = require('package-json'); -var basicAuth = require('basic-auth'); -var path = require('path'); -var jsonFile = require('json-file-plus'); -var express = require('express'); -var app = express(); -var currentVersionFeatures = require('../package.json').parseDashboardFeatures; - -// Serve public files. -app.use(express.static(path.join(__dirname,'public'))); +const currentVersionFeatures = require('../package.json').parseDashboardFeatures; var newFeaturesInLatestVersion = [] packageJson('parse-dashboard').then(latestPackage => { @@ -41,26 +35,66 @@ packageJson('parse-dashboard').then(latestPackage => { } }); -app.get('/parse-dashboard-config.json', function(req, res) { - jsonFile(configFile) - .then(config => { - config.data.apps.forEach((app) => { - if (!app.appName) { - return res.send({ success: false, error: 'An application is misconfigured, appName is required' }); - } - }); - var response = { +const port = program.port || process.env.PORT || 4040; +const allowInsecureHTTP = program.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP; + +let configFile = null; +let configFromCLI = null; +if (!program.config) { + if (!program.appId || !program.masterKey || !program.serverURL) { + console.log('You must provide either a config file or an app ID, Master Key, and server URL. See parse-dashboard --help for details.'); + process.exit(4); + } + configFromCLI = { + data: { + apps: [ + { + appId: program.appId, + serverURL: program.serverURL, + masterKey: program.masterKey, + appName: program.appName, + } + ] + } + } +} else { + configFile = program.config || path.join(__dirname, 'parse-dashboard-config.json'); + if (program.appId || program.serverURL || program.masterKey || program.appName) { + console.log('You must provide either a config file or app ID, Master Key, and server URL, not both.'); + process.exit(3); + } +} + +const p = configFile ? jsonFile(configFile) : Promise.resolve(configFromCLI); +p.then(config => { + config.data.apps.forEach(app => { + if (!app.appName) { + app.appName = app.appId; + } + }); + + const app = express(); + + // Serve public files. + app.use(express.static(path.join(__dirname,'public'))); + + // Serve the configuration. + app.get('/parse-dashboard-config.json', function(req, res) { + const response = { apps: config.data.apps, newFeaturesInLatestVersion: newFeaturesInLatestVersion, }; - var users = config.data.users; + const users = config.data.users; + + let auth = null; //If they provide auth when their config has no users, ignore the auth if (users) { - var auth = basicAuth(req); + auth = basicAuth(req); } + //Based on advice from Doug Wilson here: //https://github.com/expressjs/express/issues/2518 - var requestIsLocal = + const requestIsLocal = req.connection.remoteAddress === '127.0.0.1' || req.connection.remoteAddress === '::ffff:127.0.0.1' || req.connection.remoteAddress === '::1'; @@ -74,7 +108,7 @@ app.get('/parse-dashboard-config.json', function(req, res) { return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' }); } - var successfulAuth = + const successfulAuth = //they provided auth auth && //there are configured users @@ -86,7 +120,7 @@ app.get('/parse-dashboard-config.json', function(req, res) { }); if (successfulAuth) { //They provided correct auth - return res.send(response); + return res.json(response); } if (users || auth) { @@ -103,22 +137,30 @@ app.get('/parse-dashboard-config.json', function(req, res) { } //We shouldn't get here. Fail closed. res.send({ success: false, error: 'Something went wrong.' }); - }, error => { - if (error instanceof SyntaxError) { - res.send({ success: false, error: 'Your parse-dashboard-config.json file contains invalid JSON.' }); - } else if (error.code === 'ENOENT') { - res.send({ success: false, error: 'Your parse-dashboard-config.json file is missing.' }); - } else { - res.send({ success: false, error: 'There was a problem with your parse-dashboard-config.json file.' }); - } - }) - .catch(error => res.send({ success: false, error: 'There was a problem loading the dashboard.' })); -}); + }); -// For every other request, go to index.html. Let client-side handle the rest. -app.get('/*', function(req, res) { - res.sendFile(__dirname + '/index.html'); -}); + // For every other request, go to index.html. Let client-side handle the rest. + app.get('/*', function(req, res) { + res.sendFile(__dirname + '/index.html'); + }); + + // Start the server. + app.listen(port); -// Start the server, listening to port 4040. -app.listen(port || 4040); + console.log(`The dashboard is now available at http://localhost:${port}/`); +}, error => { + if (error instanceof SyntaxError) { + console.log('Your config file contains invalid JSON. Exiting.'); + process.exit(1); + } else if (error.code === 'ENOENT') { + console.log('Your config file is missing. Exiting.'); + process.exit(2); + } else { + console.log('There was a problem with your config file. Exiting.'); + process.exit(-1); + } +}) +.catch(error => { + console.log('There was a problem loading the dashboard. Exiting.'); + process.exit(-1); +}); diff --git a/README.md b/README.md index 4a409241ba..2be6638ff2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ # Parse Dashboard -Parse Dashboard is standalone dashboard for managing your Parse apps. You can use it to manage your [Parse Server](https://github.com/ParsePlatform/parse-server) apps and your apps that are running on [Parse.com](https://Parse.com). +Parse Dashboard is a standalone dashboard for managing your Parse apps. You can use it to manage your [Parse Server](https://github.com/ParsePlatform/parse-server) apps and your apps that are running on [Parse.com](https://Parse.com). -## Getting Started +# Getting Started -[Node.js](https://nodejs.org) version >= 4.3 is required to run the dashboard. Once you have Node you can install the dashboard by cloning this repo and running `npm install`. +[Node.js](https://nodejs.org) version >= 4.3 is required to run the dashboard. You also need to be using Parse Server version 2.1.4 or higher. Install the dashboard from `npm`. ``` -git clone git@github.com:ParsePlatform/parse-dashboard.git -cd parse-dashboard -npm install +npm install -g parse-dashboard ``` -Next add your app info into `parse-dashboard/Parse-Dashboard/parse-dashboard-config.json`. The file should match the following format, using the server URL, App ID, and Master Key from your Parse Server. The App Name can be anything you want, and is used to reference your app in the dashboard. **Make sure the server URL is a URL that can be accessed by your browser.** +You can launch the dashboard for an app with a single command by supplying an app ID, master key, URL, and name like this: + +``` +parse-dashboard --appID yourAppId --masterKey yourMasterKey --serverURL "https://example.com/parse" --appName optionalName +``` + +You can then visit the dashboard in your browser at http://localhost:4040. If you want to use a different port you can supply the --port option to parse-dashboard. You can use anything you want as the app name, or leave it out in which case the app ID will be used. + +If you want to manage multiple apps from the same dashboard, you can start the dashboard with a config file. For example, you could put your info into a file called `parse-dashboard-config.json` and then start the dashboard using `parse-dashboard --config parse-dashboard-config.json`. The file should match the following format: ``` { @@ -27,9 +33,7 @@ Next add your app info into `parse-dashboard/Parse-Dashboard/parse-dashboard-con } ``` -Ensure your Parse Server version is `>= 2.1.4`. The dashboard will not work with Parse Server instances with lower versions. - -You can also manage your apps that are hosted on Parse.com from the same dashboard. For these apps, you must specify your rest key and javascript key, and set your server url to https://api.parse.com/1. +You can also manage apps that on Parse.com from the same dashboard. In your config file, you will need to add the `restKey` and `javascriptKey` as well as the other paramaters, which you can find on `dashboard.parse.com`. Set the serverURL to `http://api.parse.com/1`: ``` { @@ -52,16 +56,19 @@ You can also manage your apps that are hosted on Parse.com from the same dashboa } ``` -Then execute `npm run dashboard` and visit [`http://localhost:4040`](http://localhost:4040) and you will be able to manage your parse apps. - ![Parse Dashboard](.github/dash-shot.png) +# Advanced Usage + ## Other options -You can set `appNameForURL` for each app to control the url of your app within the dashboard. +You can set `appNameForURL` in the config file for each app to control the url of your app within the dashboard. This can make it easier to use bookmarks or share links on your dashboard. + +## Deploying the dashboard -If you want to require a username and password to access the dashboard, you can do so by adding usernames and passwords for HTTP Basic Auth to your configuration file: +Make sure the server URLs for your apps can be accessed by your browser. If you are deploying the dashboard, then `localhost` urls will not work. +In order to securely deploy the dashboard without leaking your apps master key, you will need to use HTTPS and Basic Auth. You can do this by adding usernames and passwords for HTTP Basic Auth to your configuration file. ``` { "apps": [...], @@ -78,23 +85,23 @@ If you want to require a username and password to access the dashboard, you can } ``` -HTTPS and Basic Auth are mandatory if you are accessing the dashboard remotely instead of accessing it from `localhost`. +The deployed dashboard detects if you are using a secure connection. If you are deploying the dashboard behind a load balancer or proxy that does early SSL termination, then the app won't be able to detect that the connection is secure. In this case, you can start the dashboard with the `--allowInsecureHTTP=1` option. You will then be responsible for ensureing that your proxy or load balancer only allows HTTPS. ## Run with Docker It is easy to use it with Docker. First build the image: -``` +``` docker build -t parse-dashboard . ``` Run the image with your ``config.json`` mounted as a volume ``` -docker run -d -p 8080:4040 -v host/path/to/config.json:/src/Parse-Dashboard/parse-dashboard-config.json parse-dashboard +docker run -d -p 8080:4040 -v host/path/to/config.json:/src/Parse-Dashboard/parse-dashboard-config.json parse-dashboard ``` -By default, the container will start the app at port 4040 inside the container. However, you can run custom command as well (see ``Deploying in production`` for custom setup). +By default, the container will start the app at port 4040 inside the container. However, you can run custom command as well (see ``Deploying in production`` for custom setup). In this example, we want to run the application in production mode at port 80 of the host machine. @@ -104,32 +111,6 @@ docker run -d -p 80:8080 -v host/path/to/config.json:/src/Parse-Dashboard/parse- If you are not familiar with Docker, ``--port 8080`` with be passed in as argument to the entrypoint to form the full command ``npm start -- --port 8080``. The application will start at port 8080 inside the container and port ``8080`` will be mounted to port ``80`` on your host machine. -## Deploying in production - -If you're deploying to a provider like Heroku, or Google App Engine, the SSL endpoint is terminated early and handled by the provider and you may encounter this error: `Parse Dashboard can only be remotely accessed via HTTPS`. - -:warning: :warning: Before going further, make sure your server **cannot** be reachable via **HTTP**. See the provider documentation for force HTTPS connections to your deployment. - -Set the environment variable `PARSE_DASHBOARD_ALLOW_INSECURE_HTTP=1` to tell parse server to skip the secure tests. - -To start your server use: - -`$ npm start` - -Optionally you can use the command line arguments: - -`$ npm start -- --config path/to/config.json --port 8080 --allowInsecureHTTP=1` - -Or update you start script with the accoring configuration. - -All paramters are optional and their default values are: - - - config: parse-dashboard/Parse-Dashboard/parse-dashboard-config.json - port: 4040 - allowInsecureHTTP: false - - ## Contributing We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Dashboard guide](CONTRIBUTING.md). diff --git a/package.json b/package.json index 9c4b25ab08..a0560101f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "parse-dashboard", - "description": "The parse dashboard", "parseDashboardFeatures": [ "Data Browser", "Cloud Code Viewer", @@ -8,12 +7,19 @@ "Parse Config", "API Console" ], - "version": "2.0.0", + "description": "The Parse Dashboard", + "keywords": [ + "parse", + "dashboard" + ], + "homepage": "https://github.com/ParsePlatform/parse-dashboard", + "bugs": "https://github.com/ParsePlatform/parse-dashboard/issues", + "version": "1.0.0", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" }, - "license": "LicenseRef-LICENSE", + "license": "SEE LICENSE IN LICENSE", "files": [ "Parse-Dashboard", "bin", From 3feb693aa3f86c9584805085ffc4215a8a4ad234 Mon Sep 17 00:00:00 2001 From: Drew Gross Date: Thu, 17 Mar 2016 09:46:03 -0700 Subject: [PATCH 2/3] Fix loading of default config --- Parse-Dashboard/index.js | 59 +++++++++++++++++++++++++--------------- package.json | 26 +++++++++--------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Parse-Dashboard/index.js b/Parse-Dashboard/index.js index 96e0cc0348..7b227607ab 100644 --- a/Parse-Dashboard/index.js +++ b/Parse-Dashboard/index.js @@ -26,7 +26,7 @@ program.parse(process.argv); const currentVersionFeatures = require('../package.json').parseDashboardFeatures; -var newFeaturesInLatestVersion = [] +var newFeaturesInLatestVersion = []; packageJson('parse-dashboard').then(latestPackage => { if (latestPackage.parseDashboardFeatures instanceof Array) { newFeaturesInLatestVersion = parseDashboardFeatures.filter(feature => { @@ -38,34 +38,44 @@ packageJson('parse-dashboard').then(latestPackage => { const port = program.port || process.env.PORT || 4040; const allowInsecureHTTP = program.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP; +let explicitConfigFileProvided = !!program.config; let configFile = null; let configFromCLI = null; if (!program.config) { - if (!program.appId || !program.masterKey || !program.serverURL) { - console.log('You must provide either a config file or an app ID, Master Key, and server URL. See parse-dashboard --help for details.'); - process.exit(4); - } - configFromCLI = { - data: { - apps: [ - { - appId: program.appId, - serverURL: program.serverURL, - masterKey: program.masterKey, - appName: program.appName, - } - ] - } + if (program.serverURL && program.masterKey && program.appId) { + configFromCLI = { + data: { + apps: [ + { + appId: program.appId, + serverURL: program.serverURL, + masterKey: program.masterKey, + appName: program.appName, + }, + ] + } + }; + } else if (!program.serverURL && !program.masterKey && !program.appName) { + configFile = path.join(__dirname, 'parse-dashboard-config.json'); } } else { - configFile = program.config || path.join(__dirname, 'parse-dashboard-config.json'); + configFile = program.config; if (program.appId || program.serverURL || program.masterKey || program.appName) { - console.log('You must provide either a config file or app ID, Master Key, and server URL, not both.'); + console.log('You must provide either a config file or required CLI options (app ID, Master Key, and server URL); not both.'); process.exit(3); } } -const p = configFile ? jsonFile(configFile) : Promise.resolve(configFromCLI); +let p = null; +if (configFile) { + p = jsonFile(configFile); +} else if (configFromCLI) { + p = Promise.resolve(configFromCLI); +} else { + //Failed to load default config file. + console.log('You must provide either a config file or an app ID, Master Key, and server URL. See parse-dashboard --help for details.'); + process.exit(4); +} p.then(config => { config.data.apps.forEach(app => { if (!app.appName) { @@ -153,10 +163,15 @@ p.then(config => { console.log('Your config file contains invalid JSON. Exiting.'); process.exit(1); } else if (error.code === 'ENOENT') { - console.log('Your config file is missing. Exiting.'); - process.exit(2); + if (explicitConfigFileProvided) { + console.log('Your config file is missing. Exiting.'); + process.exit(2); + } else { + console.log('You must provide either a config file or required CLI options (app ID, Master Key, and server URL); not both.'); + process.exit(3); + } } else { - console.log('There was a problem with your config file. Exiting.'); + console.log('There was a problem with your config. Exiting.'); process.exit(-1); } }) diff --git a/package.json b/package.json index a0560101f8..97d4827f6d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ ], "homepage": "https://github.com/ParsePlatform/parse-dashboard", "bugs": "https://github.com/ParsePlatform/parse-dashboard/issues", - "version": "1.0.0", + "version": "1.0.2", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" @@ -27,35 +27,35 @@ "LICENSE" ], "dependencies": { - "babel-runtime": "~5.8.25", "basic-auth": "^1.0.3", "commander": "^2.9.0", "express": "^4.13.4", - "history": "~1.9.1", - "immutable": "~3.7.5", "json-file-plus": "^3.2.0", - "marked": "^0.3.5", - "package-json": "^2.3.1", - "parse": "1.6.14", - "prismjs": "~1.2.0", - "react": "^0.14.0", - "react-dnd": "~2.0.2", - "react-dnd-html5-backend": "~2.0.0", - "react-dom": "^0.14.0", - "react-router": "2.0.0" + "package-json": "^2.3.1" }, "devDependencies": { "babel-core": "~5.8.12", "babel-loader": "~5.3.0", "babel-plugin-remove-proptypes": "~1.0.0", + "babel-runtime": "~5.8.25", "css-loader": "~0.18.0", "file-loader": "^0.8.5", + "history": "~1.9.1", "http-server": "~0.8.5", + "immutable": "~3.7.5", "immutable-devtools": "~0.0.4", "jest-cli": "^0.7.1", "js-beautify": "~1.5.0", + "marked": "^0.3.5", "node-sass": "~3.4.2", + "parse": "1.6.14", + "prismjs": "~1.2.0", + "react": "^0.14.0", "react-addons-test-utils": "^0.14.2", + "react-dnd": "~2.0.2", + "react-dnd-html5-backend": "~2.0.0", + "react-dom": "^0.14.0", + "react-router": "2.0.0", "sass-loader": "~3.1.2", "style-loader": "~0.12.3", "svg-prep": "~1.0.0", From 7fe14aa24ca48d1d1b084bb706de6b99b3f36c34 Mon Sep 17 00:00:00 2001 From: "Peter J. Shin" Date: Thu, 17 Mar 2016 10:42:44 -0700 Subject: [PATCH 3/3] Fixing typo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2be6638ff2..e861c9c1fc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ npm install -g parse-dashboard You can launch the dashboard for an app with a single command by supplying an app ID, master key, URL, and name like this: ``` -parse-dashboard --appID yourAppId --masterKey yourMasterKey --serverURL "https://example.com/parse" --appName optionalName +parse-dashboard --appId yourAppId --masterKey yourMasterKey --serverURL "https://example.com/parse" --appName optionalName ``` You can then visit the dashboard in your browser at http://localhost:4040. If you want to use a different port you can supply the --port option to parse-dashboard. You can use anything you want as the app name, or leave it out in which case the app ID will be used.