From 070f64903023b2bfd8359c370efbae67566b2ddf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Jan 2023 10:16:24 +0100 Subject: [PATCH 01/15] refactor: Bump json5 from 2.2.0 to 2.2.3 (#2357) --- package-lock.json | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcaf4c20fd..6c0924c237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -244,12 +244,6 @@ "integrity": "sha512-saJTYECxUSv7eSpnXw0XIEvUkP9x4s/x2mm3TVX7k4rIFS6f5TjBih1B5h437WzIhHQjid+d8ouQzPQskMervQ==", "dev": true }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, "node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -11814,13 +11808,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true }, "jsonfile": { "version": "6.1.0", From 6cf32014b989b557cafc2564ff73e36816b2e067 Mon Sep 17 00:00:00 2001 From: Shruti Chaturvedi Date: Fri, 23 Dec 2022 11:03:18 +0530 Subject: [PATCH 02/15] Integrate Uffizzi --- docker-compose.uffizzi.yml | 75 +++++++++++++++++++++++++++++++++++ nginx-uffizzi/html/index.html | 20 ++++++++++ nginx-uffizzi/nginx.conf | 35 ++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 docker-compose.uffizzi.yml create mode 100644 nginx-uffizzi/html/index.html create mode 100644 nginx-uffizzi/nginx.conf diff --git a/docker-compose.uffizzi.yml b/docker-compose.uffizzi.yml new file mode 100644 index 0000000000..7d2805a5ce --- /dev/null +++ b/docker-compose.uffizzi.yml @@ -0,0 +1,75 @@ +version: '3' + +# uffizzi integration +x-uffizzi: + ingress: + service: nginx + port: 8081 + continuous_previews: + deploy_preview_when_pull_request_is_opened: true + delete_preview_when_pull_request_is_closed: true + share_to_github: true + +services: + + postgres: + image: postgres + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=postgres + ports: + - "5432:5432" + deploy: + resources: + limits: + memory: 1000M + volumes: + - postgres_data:/var/lib/postgresql + + parse: + image: parseplatform/parse-server:latest + environment: + - PARSE_SERVER_APPLICATION_ID=parse + - PARSE_SERVER_MASTER_KEY=parse@master123! + - PARSE_SERVER_DATABASE_URI=postgresql://postgres:password@localhost:5432/postgres + - PARSE_SERVER_MOUNT_PATH=/parse + - PORT=1337 + ports: + - '1337:1337' + deploy: + resources: + limits: + memory: 1000M + + dashboard: + build: + context: . + dockerfile: ./Dockerfile + ports: + - "4040:4040" + environment: + - PARSE_DASHBOARD_MASTER_KEY=parse@master123! + - PARSE_DASHBOARD_APP_ID=parse + - PARSE_DASHBOARD_APP_NAME=MyParseApp + - PARSE_DASHBOARD_USER_ID=admin + - PARSE_DASHBOARD_USER_PASSWORD=password + - MOUNT_PATH=/dashboard + - PARSE_DASHBOARD_ALLOW_INSECURE_HTTP=1 + entrypoint: /bin/sh + command: + - "-c" + - "PARSE_DASHBOARD_SERVER_URL=$$UFFIZZI_URL/parse node Parse-Dashboard/index.js" + deploy: + resources: + limits: + memory: 1000M + + nginx: + image: nginx:alpine + volumes: + - ./nginx-uffizzi:/etc/nginx + - ./nginx-uffizzi/html:/usr/share/nginx/html + +volumes: + postgres_data: diff --git a/nginx-uffizzi/html/index.html b/nginx-uffizzi/html/index.html new file mode 100644 index 0000000000..1f624f6a33 --- /dev/null +++ b/nginx-uffizzi/html/index.html @@ -0,0 +1,20 @@ + + + + + + +Parse Dashboard Preview + + + + + +

Endpoint:

+ + Click to Visit Parse Dashboard + + + + + diff --git a/nginx-uffizzi/nginx.conf b/nginx-uffizzi/nginx.conf new file mode 100644 index 0000000000..6ceab7c4e8 --- /dev/null +++ b/nginx-uffizzi/nginx.conf @@ -0,0 +1,35 @@ +events { + worker_connections 1024; #default +} +http { + + server { + + listen 8081; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + location /dashboard { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-NginX-Proxy true; + proxy_pass http://localhost:4040/dashboard/; + proxy_ssl_session_reuse off; + proxy_set_header Host $http_host; + proxy_redirect off; + } + + location /parse { + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + keepalive_requests 10; + keepalive_timeout 75s; + proxy_pass http://localhost:1337/parse/; + proxy_http_version 1.1; + } + } +} From 98552584df4d8d75d65d3e394b4acad522117a96 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 21 Jan 2023 02:20:37 +1100 Subject: [PATCH 03/15] fix: Blank screen shown if server is unreachable; unsupported pages are accessible via direct URLs (#2363) --- src/dashboard/Dashboard.js | 6 +- src/dashboard/Dashboard.scss | 21 ++++ src/dashboard/DashboardView.react.js | 173 ++++++++++++++++++++------- 3 files changed, 156 insertions(+), 44 deletions(-) diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js index 3a639cdb79..9993dc0722 100644 --- a/src/dashboard/Dashboard.js +++ b/src/dashboard/Dashboard.js @@ -138,21 +138,21 @@ export default class Dashboard extends React.Component { if (error.code === 100) { app.serverInfo = { error: 'unable to connect to server', - enabledFeatures: {}, + features: {}, parseServerVersion: 'unknown' } return Promise.resolve(app); } else if (error.code === 107) { app.serverInfo = { error: 'server version too low', - enabledFeatures: {}, + features: {}, parseServerVersion: 'unknown' } return Promise.resolve(app); } else { app.serverInfo = { error: error.message || 'unknown error', - enabledFeatures: {}, + features: {}, parseServerVersion: 'unknown' } return Promise.resolve(app); diff --git a/src/dashboard/Dashboard.scss b/src/dashboard/Dashboard.scss index 140c11b2bd..c99ad86341 100644 --- a/src/dashboard/Dashboard.scss +++ b/src/dashboard/Dashboard.scss @@ -17,4 +17,25 @@ body:global(.expanded) { .content { margin-left: $sidebarCollapsedWidth; } +} + +.loadingError { + font-size: 58px; + color: #ffffff; +} + +.empty { + position: relative; + background: #1e3b4d; + min-height: 100vh; + text-align: center; +} + +.cloud { + width: 170px; + height: 170px; + border-radius: 100%; + padding-top: 30px; + background: #3E5566; + margin: 0 auto 14px auto; } \ No newline at end of file diff --git a/src/dashboard/DashboardView.react.js b/src/dashboard/DashboardView.react.js index 15972fd8e6..4846ae4c72 100644 --- a/src/dashboard/DashboardView.react.js +++ b/src/dashboard/DashboardView.react.js @@ -8,82 +8,125 @@ import React from 'react'; import Sidebar from 'components/Sidebar/Sidebar.react'; import styles from 'dashboard/Dashboard.scss'; +import Icon from 'components/Icon/Icon.react'; +import baseStyles from 'stylesheets/base.scss'; +import Button from 'components/Button/Button.react'; import { CurrentApp } from 'context/currentApp'; export default class DashboardView extends React.Component { static contextType = CurrentApp; - /* A DashboardView renders two pieces: the sidebar, and the app itself */ + + constructor() { + super(); + this.state = { + route: '', + }; + } + + componentDidUpdate() { + this.onRouteChanged(); + } + componentDidMount() { + this.onRouteChanged(); + } + + onRouteChanged() { + const appId = this.context.applicationId; + const path = this.props.location?.pathname ?? window.location.pathname; + const route = path.split(appId)[1].split('/')[1]; + if (route !== this.state.route) { + this.setState({ route }); + } + } + render() { let sidebarChildren = null; if (typeof this.renderSidebar === 'function') { sidebarChildren = this.renderSidebar(); } - let appSlug = (this.context ? this.context.slug : ''); + let appSlug = this.context ? this.context.slug : ''; if (!this.context.hasCheckedForMigraton) { - this.context.getMigrations().promise - .then(() => this.forceUpdate(), () => {}); + this.context.getMigrations().promise.then( + () => this.forceUpdate(), + () => {} + ); } let features = this.context.serverInfo.features; let coreSubsections = []; - if (features.schemas && + if ( + features.schemas && features.schemas.addField && features.schemas.removeField && features.schemas.addClass && - features.schemas.removeClass) { + features.schemas.removeClass + ) { coreSubsections.push({ name: 'Browser', - link: '/browser' + link: '/browser', }); } if (features.cloudCode && features.cloudCode.viewCode) { coreSubsections.push({ name: 'Cloud Code', - link: '/cloud_code' + link: '/cloud_code', }); } //webhooks requires removal of heroku link code, then it should work. - if (features.hooks && features.hooks.create && features.hooks.read && features.hooks.update && features.hooks.delete) { + if ( + features.hooks && + features.hooks.create && + features.hooks.read && + features.hooks.update && + features.hooks.delete + ) { coreSubsections.push({ name: 'Webhooks', - link: '/webhooks' + link: '/webhooks', }); } if (features.cloudCode && features.cloudCode.jobs) { coreSubsections.push({ name: 'Jobs', - link: '/jobs' + link: '/jobs', }); } - if (features.logs && Object.keys(features.logs).some(key => features.logs[key])) { + if ( + features.logs && + Object.keys(features.logs).some((key) => features.logs[key]) + ) { coreSubsections.push({ name: 'Logs', - link: '/logs' + link: '/logs', }); } - if (features.globalConfig && + if ( + features.globalConfig && features.globalConfig.create && features.globalConfig.read && features.globalConfig.update && - features.globalConfig.delete) { + features.globalConfig.delete + ) { coreSubsections.push({ name: 'Config', - link: '/config' + link: '/config', }); } - coreSubsections.push({ - name: 'API Console', - link: '/api_console' - }); + if (!this.context.serverInfo.error) { + coreSubsections.push({ + name: 'API Console', + link: '/api_console', + }); + } if (this.context.migration) { coreSubsections.push({ @@ -96,21 +139,21 @@ export default class DashboardView extends React.Component { if (features.push && features.push.immediatePush) { pushSubsections.push({ name: 'Send New Push', - link: '/push/new' + link: '/push/new', }); } if (features.push && features.push.storedPushData) { pushSubsections.push({ name: 'Past Pushes', - link: '/push/activity' + link: '/push/activity', }); } if (features.push && features.push.pushAudiences) { pushSubsections.push({ name: 'Audiences', - link: '/push/audiences' + link: '/push/audiences', }); } @@ -195,7 +238,7 @@ export default class DashboardView extends React.Component { }); }*/ - let appSidebarSections = [] + let appSidebarSections = []; if (coreSubsections.length > 0) { appSidebarSections.push({ @@ -211,7 +254,7 @@ export default class DashboardView extends React.Component { name: 'Push', icon: 'push-outline', link: '/push', - style: {paddingLeft: '16px'}, + style: { paddingLeft: '16px' }, subsections: pushSubsections, }); } @@ -221,7 +264,7 @@ export default class DashboardView extends React.Component { name: 'Analytics', icon: 'analytics-outline', link: '/analytics', - subsections: analyticsSidebarSections + subsections: analyticsSidebarSections, }); } @@ -230,29 +273,77 @@ export default class DashboardView extends React.Component { name: 'App Settings', icon: 'gear-solid', link: '/settings', - subsections: settingsSections + subsections: settingsSections, }); } let sidebar = ( - - {sidebarChildren} - ); + {sidebarChildren} + + ); + + let content =
{this.renderContent()}
; + const canRoute = [...coreSubsections, ...pushSubsections] + .map(({ link }) => link.split('/')[1]) + .includes(this.state.route); + + if (!canRoute) { + content = ( +
+
+
+ +
+
Feature unavailable
+
+
+ ); + } + + if (this.context.serverInfo.error) { + content = ( +
+
+
+ +
+
+ {this.context.serverInfo.error.replace(/-/g, '\u2011')} +
+
+
+ ); + } return (
-
- {this.renderContent()} -
+ {content} {sidebar}
); From 7180e9c29f64005b4cb9bbc7d51b66afb2c887e0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 20 Jan 2023 15:23:17 +0000 Subject: [PATCH 04/15] chore(release): 5.1.0-alpha.2 [skip ci] # [5.1.0-alpha.2](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2023-01-20) ### Bug Fixes * Blank screen shown if server is unreachable; unsupported pages are accessible via direct URLs ([#2363](https://github.com/ParsePlatform/parse-dashboard/issues/2363)) ([9855258](https://github.com/ParsePlatform/parse-dashboard/commit/98552584df4d8d75d65d3e394b4acad522117a96)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 011fc1f9f2..2689f737f2 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.2](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2023-01-20) + + +### Bug Fixes + +* Blank screen shown if server is unreachable; unsupported pages are accessible via direct URLs ([#2363](https://github.com/ParsePlatform/parse-dashboard/issues/2363)) ([9855258](https://github.com/ParsePlatform/parse-dashboard/commit/98552584df4d8d75d65d3e394b4acad522117a96)) + # [5.1.0-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/5.0.0...5.1.0-alpha.1) (2022-11-05) diff --git a/package-lock.json b/package-lock.json index 6c0924c237..ea0cd7c96e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.1", + "version": "5.1.0-alpha.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0df380e9b6..9b909779dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.1", + "version": "5.1.0-alpha.2", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From 33df0495a02c4e77f48b3566032bf5686227cce7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 21 Jan 2023 09:57:08 +1100 Subject: [PATCH 05/15] feat: Add schema export (#2362) --- src/dashboard/Data/Browser/Browser.react.js | 52 ++++++++++++++- .../Data/Browser/BrowserToolbar.react.js | 5 ++ .../Data/Browser/ExportSchemaDialog.react.js | 65 +++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/dashboard/Data/Browser/ExportSchemaDialog.react.js diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 8e35357351..9861b363d2 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -21,6 +21,7 @@ import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSel import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react'; import EditRowDialog from 'dashboard/Data/Browser/EditRowDialog.react'; import ExportSelectedRowsDialog from 'dashboard/Data/Browser/ExportSelectedRowsDialog.react'; +import ExportSchemaDialog from 'dashboard/Data/Browser/ExportSchemaDialog.react'; import { List, Map } from 'immutable'; import Notification from 'dashboard/Data/Browser/Notification.react'; import Parse from 'parse'; @@ -56,6 +57,7 @@ class Browser extends DashboardView { showRemoveColumnDialog: false, showDropClassDialog: false, showExportDialog: false, + showExportSchemaDialog: false, showAttachRowsDialog: false, showEditRowDialog: false, showPointerKeyDialog: false, @@ -88,7 +90,7 @@ class Browser extends DashboardView { requiredColumnFields: [], useMasterKey: true, - currentUser: Parse.User.current() + currentUser: Parse.User.current(), }; this.prefetchData = this.prefetchData.bind(this); @@ -114,6 +116,7 @@ class Browser extends DashboardView { this.confirmCloneSelectedRows = this.confirmCloneSelectedRows.bind(this); this.cancelCloneSelectedRows = this.cancelCloneSelectedRows.bind(this); this.showExportSelectedRowsDialog = this.showExportSelectedRowsDialog.bind(this); + this.showExportSchemaDialog = this.showExportSchemaDialog.bind(this); this.confirmExportSelectedRows = this.confirmExportSelectedRows.bind(this); this.cancelExportSelectedRows = this.cancelExportSelectedRows.bind(this); this.getClassRelationColumns = this.getClassRelationColumns.bind(this); @@ -326,6 +329,37 @@ class Browser extends DashboardView { }); } + async exportSchema(className, all) { + try { + this.showNote('Exporting schema...'); + this.setState({ showExportSchemaDialog: false }); + let schema = []; + if (all) { + schema = await Parse.Schema.all(); + } else { + schema = await new Parse.Schema(className).get(); + } + const element = document.createElement('a'); + const file = new Blob( + [ + JSON.stringify( + schema, + null, + 2, + ), + ], + { type: 'application/json' } + ); + element.href = URL.createObjectURL(file); + element.download = `${all ? 'schema' : className}.json`; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + document.body.removeChild(element); + } catch (msg) { + this.showNote(msg, true); + } + } + newColumn(payload, required) { return this.props.schema.dispatch(ActionTypes.ADD_COLUMN, payload) .then(() => { @@ -1089,6 +1123,7 @@ class Browser extends DashboardView { this.state.showRemoveColumnDialog || this.state.showDropClassDialog || this.state.showExportDialog || + this.state.showExportSchema || this.state.rowsToDelete || this.state.showAttachRowsDialog || this.state.showAttachSelectedRowsDialog || @@ -1249,6 +1284,12 @@ class Browser extends DashboardView { }); } + showExportSchemaDialog() { + this.setState({ + showExportSchemaDialog: true + }) + } + cancelExportSelectedRows() { this.setState({ rowsToExport: null @@ -1545,6 +1586,7 @@ class Browser extends DashboardView { onEditSelectedRow={this.showEditRowDialog} onEditPermissions={this.onDialogToggle} onExportSelectedRows={this.showExportSelectedRowsDialog} + onExportSchema={this.showExportSchemaDialog} onSaveNewRow={this.saveNewRow} onShowPointerKey={this.showPointerKeyDialog} @@ -1659,6 +1701,14 @@ class Browser extends DashboardView { onCancel={() => this.setState({ showExportDialog: false })} onConfirm={() => this.exportClass(className)} /> ); + } else if (this.state.showExportSchemaDialog) { + extras = ( + this.setState({ showExportSchemaDialog: false })} + onConfirm={(...args) => this.exportSchema(...args)} /> + ); } else if (this.state.showAttachRowsDialog) { extras = ( onExportSelectedRows({ '*': true })} /> + onExportSchema()} + /> )} {onAddRow &&
} diff --git a/src/dashboard/Data/Browser/ExportSchemaDialog.react.js b/src/dashboard/Data/Browser/ExportSchemaDialog.react.js new file mode 100644 index 0000000000..0f2d7d84c2 --- /dev/null +++ b/src/dashboard/Data/Browser/ExportSchemaDialog.react.js @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +import Modal from 'components/Modal/Modal.react'; +import React from 'react'; +import Dropdown from 'components/Dropdown/Dropdown.react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; +import Option from 'components/Dropdown/Option.react'; +import Toggle from 'components/Toggle/Toggle.react'; + +export default class ExportSchemaDialog extends React.Component { + constructor(props) { + super(); + const classes = Object.keys(props.schema.toObject()).sort(); + classes.sort((a, b) => { + if (a[0] === '_' && b[0] !== '_') { + return -1; + } + if (b[0] === '_' && a[0] !== '_') { + return 1; + } + return a.toUpperCase() < b.toUpperCase() ? -1 : 1; + }); + this.state = { + all: false, + className: props.className, + classes + }; + } + + + render() { + return ( + this.props.onConfirm(this.state.className, this.state.all)}> + {!this.state.all && + } + input={ + this.setState({ className })}> + {this.state.classes.map(schema => )} + + } /> + } + } + input={ {this.setState({all})}} />} /> + + ); + } +} From 9d5c72cece3c052fcc8deb72978464ba89bec9b5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 20 Jan 2023 22:59:04 +0000 Subject: [PATCH 06/15] chore(release): 5.1.0-alpha.3 [skip ci] # [5.1.0-alpha.3](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2023-01-20) ### Features * Add schema export ([#2362](https://github.com/ParsePlatform/parse-dashboard/issues/2362)) ([33df049](https://github.com/ParsePlatform/parse-dashboard/commit/33df0495a02c4e77f48b3566032bf5686227cce7)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 2689f737f2..92d3a4e371 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.3](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2023-01-20) + + +### Features + +* Add schema export ([#2362](https://github.com/ParsePlatform/parse-dashboard/issues/2362)) ([33df049](https://github.com/ParsePlatform/parse-dashboard/commit/33df0495a02c4e77f48b3566032bf5686227cce7)) + # [5.1.0-alpha.2](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2023-01-20) diff --git a/package-lock.json b/package-lock.json index ea0cd7c96e..e691238cef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9b909779dd..a974612b5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From 7fa5a7f27ed76e6b698dc541db2dc5d3544a410e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Wed, 25 Jan 2023 13:56:47 +0100 Subject: [PATCH 07/15] ci: Fix timeout of circular dependency check (#2371) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b1dee0b0d..60975be7fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - run: npm run lint check-circular: name: Circular Dependencies - timeout-minutes: 5 + timeout-minutes: 15 runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 9ea95fc62103b52cf4fac1d1b567334b5298b318 Mon Sep 17 00:00:00 2001 From: Ziv Chen Date: Wed, 25 Jan 2023 15:00:03 +0200 Subject: [PATCH 08/15] fix: Add dashboard option `cookieSessionMaxAge` to keep user logged in across browser sessions (#2366) --- Parse-Dashboard/Authentication.js | 5 ++--- Parse-Dashboard/app.js | 2 +- Parse-Dashboard/index.js | 2 ++ Parse-Dashboard/server.js | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Parse-Dashboard/Authentication.js b/Parse-Dashboard/Authentication.js index 0a6beee442..2f003c3c49 100644 --- a/Parse-Dashboard/Authentication.js +++ b/Parse-Dashboard/Authentication.js @@ -54,14 +54,13 @@ function initialize(app, options) { }); var cookieSessionSecret = options.cookieSessionSecret || require('crypto').randomBytes(64).toString('hex'); + const cookieSessionMaxAge = options.cookieSessionMaxAge; app.use(require('connect-flash')()); app.use(require('body-parser').urlencoded({ extended: true })); app.use(require('cookie-session')({ key : 'parse_dash', secret : cookieSessionSecret, - cookie : { - maxAge: (2 * 7 * 24 * 60 * 60 * 1000) // 2 weeks - } + maxAge : cookieSessionMaxAge })); app.use(passport.initialize()); app.use(passport.session()); diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 0149b8c634..ed03d51f70 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -68,7 +68,7 @@ module.exports = function(config, options) { const users = config.users; const useEncryptedPasswords = config.useEncryptedPasswords ? true : false; const authInstance = new Authentication(users, useEncryptedPasswords, mountPath); - authInstance.initialize(app, { cookieSessionSecret: options.cookieSessionSecret }); + authInstance.initialize(app, { cookieSessionSecret: options.cookieSessionSecret, cookieSessionMaxAge: options.cookieSessionMaxAge }); // CSRF error handler app.use(function (err, req, res, next) { diff --git a/Parse-Dashboard/index.js b/Parse-Dashboard/index.js index d4694d7a42..6217df3e95 100644 --- a/Parse-Dashboard/index.js +++ b/Parse-Dashboard/index.js @@ -28,6 +28,8 @@ program.option('--trustProxy [trustProxy]', 'set this flag when you are behind a program.option('--cookieSessionSecret [cookieSessionSecret]', 'set the cookie session secret, defaults to a random string. You should set that value if you want sessions to work across multiple server, or across restarts'); program.option('--createUser', 'helper tool to allow you to generate secure user passwords and secrets. Use this on trusted devices only.'); program.option('--createMFA', 'helper tool to allow you to generate multi-factor authentication secrets.'); +program.option('--cookieSessionMaxAge [cookieSessionMaxAge]', '(Optional) Sets the time in seconds for when the session cookie will be deleted and the dashboard user has to re-login; if no value is set then the cookie will be deleted when the browser session ends.'); + program.action(async (options) => { for (const key in options) { const func = CLIHelper[key]; diff --git a/Parse-Dashboard/server.js b/Parse-Dashboard/server.js index 2d21d8a6ba..76ac4bc398 100644 --- a/Parse-Dashboard/server.js +++ b/Parse-Dashboard/server.js @@ -19,6 +19,7 @@ module.exports = (options) => { const allowInsecureHTTP = options.allowInsecureHTTP || process.env.PARSE_DASHBOARD_ALLOW_INSECURE_HTTP; const cookieSessionSecret = options.cookieSessionSecret || process.env.PARSE_DASHBOARD_COOKIE_SESSION_SECRET; const trustProxy = options.trustProxy || process.env.PARSE_DASHBOARD_TRUST_PROXY; + const cookieSessionMaxAge = options.cookieSessionMaxAge || process.env.PARSE_DASHBOARD_COOKIE_SESSION_MAX_AGE; const dev = options.dev; if (trustProxy && allowInsecureHTTP) { @@ -145,7 +146,7 @@ module.exports = (options) => { if (allowInsecureHTTP || trustProxy || dev) app.enable('trust proxy'); config.data.trustProxy = trustProxy; - let dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev }; + let dashboardOptions = { allowInsecureHTTP, cookieSessionSecret, dev, cookieSessionMaxAge }; app.use(mountPath, parseDashboard(config.data, dashboardOptions)); let server; if(!configSSLKey || !configSSLCert){ From d9105e79f5d3fcb6387c7d6c747e14b3e598a5e3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Jan 2023 13:02:08 +0000 Subject: [PATCH 09/15] chore(release): 5.1.0-alpha.4 [skip ci] # [5.1.0-alpha.4](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2023-01-25) ### Bug Fixes * Add dashboard option `cookieSessionMaxAge` to keep user logged in across browser sessions ([#2366](https://github.com/ParsePlatform/parse-dashboard/issues/2366)) ([9ea95fc](https://github.com/ParsePlatform/parse-dashboard/commit/9ea95fc62103b52cf4fac1d1b567334b5298b318)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 92d3a4e371..71cefa1aab 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.4](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2023-01-25) + + +### Bug Fixes + +* Add dashboard option `cookieSessionMaxAge` to keep user logged in across browser sessions ([#2366](https://github.com/ParsePlatform/parse-dashboard/issues/2366)) ([9ea95fc](https://github.com/ParsePlatform/parse-dashboard/commit/9ea95fc62103b52cf4fac1d1b567334b5298b318)) + # [5.1.0-alpha.3](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2023-01-20) diff --git a/package-lock.json b/package-lock.json index e691238cef..e53581a3d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.3", + "version": "5.1.0-alpha.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a974612b5c..d187e78fa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.3", + "version": "5.1.0-alpha.4", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From 9eb36a183b8b337960f6e8563ad686958001a22b Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Jan 2023 00:21:11 +1100 Subject: [PATCH 10/15] feat: Add export all rows of a class and export in JSON format (#2361) --- src/dashboard/Data/Browser/Browser.react.js | 209 ++++++++++++------ .../Browser/ExportSelectedRowsDialog.react.js | 70 +++++- .../Browser/ExportSelectedRowsDialog.scss | 9 + 3 files changed, 211 insertions(+), 77 deletions(-) create mode 100644 src/dashboard/Data/Browser/ExportSelectedRowsDialog.scss diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 9861b363d2..46e18e8947 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -72,6 +72,8 @@ class Browser extends DashboardView { filters: new List(), ordering: '-createdAt', selection: {}, + exporting: false, + exportingCount: 0, data: null, lastMax: -1, @@ -1296,15 +1298,12 @@ class Browser extends DashboardView { }); } - async confirmExportSelectedRows(rows) { - this.setState({ rowsToExport: null }); + async confirmExportSelectedRows(rows, type, indentation) { + this.setState({ rowsToExport: null, exporting: true, exportingCount: 0 }); const className = this.props.params.className; const query = new Parse.Query(className); - if (rows['*']) { - // Export all - query.limit(10000); - } else { + if (!rows['*']) { // Export selected const objectIds = []; for (const objectId in this.state.rowsToExport) { @@ -1314,75 +1313,136 @@ class Browser extends DashboardView { query.limit(objectIds.length); } - const classColumns = this.getClassColumns(className, false); - // create object with classColumns as property keys needed for ColumnPreferences.getOrder function - const columnsObject = {}; - classColumns.forEach((column) => { - columnsObject[column.name] = column; - }); - // get ordered list of class columns - const columns = ColumnPreferences.getOrder( - columnsObject, - this.context.applicationId, - className - ).filter(column => column.visible); + const processObjects = (objects) => { + const classColumns = this.getClassColumns(className, false); + // create object with classColumns as property keys needed for ColumnPreferences.getOrder function + const columnsObject = {}; + classColumns.forEach((column) => { + columnsObject[column.name] = column; + }); + // get ordered list of class columns + const columns = ColumnPreferences.getOrder( + columnsObject, + this.context.applicationId, + className + ).filter((column) => column.visible); + + if (type === '.json') { + const element = document.createElement('a'); + const file = new Blob( + [ + JSON.stringify( + objects.map((obj) => { + const json = obj._toFullJSON(); + delete json.__type; + return json; + }), + null, + indentation ? 2 : null, + ), + ], + { type: 'application/json' } + ); + element.href = URL.createObjectURL(file); + element.download = `${className}.json`; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + document.body.removeChild(element); + return; + } - const objects = await query.find({ useMasterKey: true }); - let csvString = columns.map(column => column.name).join(',') + '\n'; - for (const object of objects) { - const row = columns.map(column => { - const type = columnsObject[column.name].type; - if (column.name === 'objectId') { - return object.id; - } else if (type === 'Relation' || type === 'Pointer') { - if (object.get(column.name)) { - return object.get(column.name).id - } else { - return '' - } - } else { - let colValue; - if (column.name === 'ACL') { - colValue = object.getACL(); - } else { - colValue = object.get(column.name); - } - // Stringify objects and arrays - if (Object.prototype.toString.call(colValue) === '[object Object]' || Object.prototype.toString.call(colValue) === '[object Array]') { - colValue = JSON.stringify(colValue); - } - if(typeof colValue === 'string') { - if (colValue.includes('"')) { - // Has quote in data, escape and quote - // If the value contains both a quote and delimiter, adding quotes and escaping will take care of both scenarios - colValue = colValue.split('"').join('""'); - return `"${colValue}"`; - } else if (colValue.includes(',')) { - // Has delimiter in data, surround with quote (which the value doesn't already contain) - return `"${colValue}"`; + let csvString = columns.map((column) => column.name).join(',') + '\n'; + for (const object of objects) { + const row = columns + .map((column) => { + const type = columnsObject[column.name].type; + if (column.name === 'objectId') { + return object.id; + } else if (type === 'Relation' || type === 'Pointer') { + if (object.get(column.name)) { + return object.get(column.name).id; + } else { + return ''; + } } else { - // No quote or delimiter, just include plainly - return `${colValue}`; + let colValue; + if (column.name === 'ACL') { + colValue = object.getACL(); + } else { + colValue = object.get(column.name); + } + // Stringify objects and arrays + if ( + Object.prototype.toString.call(colValue) === + '[object Object]' || + Object.prototype.toString.call(colValue) === '[object Array]' + ) { + colValue = JSON.stringify(colValue); + } + if (typeof colValue === 'string') { + if (colValue.includes('"')) { + // Has quote in data, escape and quote + // If the value contains both a quote and delimiter, adding quotes and escaping will take care of both scenarios + colValue = colValue.split('"').join('""'); + return `"${colValue}"`; + } else if (colValue.includes(',')) { + // Has delimiter in data, surround with quote (which the value doesn't already contain) + return `"${colValue}"`; + } else { + // No quote or delimiter, just include plainly + return `${colValue}`; + } + } else if (colValue === undefined) { + // Export as empty CSV field + return ''; + } else { + return `${colValue}`; + } } - } else if (colValue === undefined) { - // Export as empty CSV field - return ''; - } else { - return `${colValue}`; + }) + .join(','); + csvString += row + '\n'; + } + + // Deliver to browser to download file + const element = document.createElement('a'); + const file = new Blob([csvString], { type: 'text/csv' }); + element.href = URL.createObjectURL(file); + element.download = `${className}.csv`; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + document.body.removeChild(element); + }; + + if (!rows['*']) { + const objects = await query.find({ useMasterKey: true }); + processObjects(objects); + this.setState({ exporting: false, exportingCount: objects.length }); + } else { + let batch = []; + query.eachBatch( + (obj) => { + batch.push(...obj); + if (batch.length % 10 === 0) { + this.setState({ exportingCount: batch.length }); } - } - }).join(','); - csvString += row + '\n'; + const one_gigabyte = Math.pow(2, 30); + const size = + new TextEncoder().encode(JSON.stringify(batch)).length / + one_gigabyte; + if (size.length > 1) { + processObjects(batch); + batch = []; + } + if (obj.length !== 100) { + processObjects(batch); + batch = []; + this.setState({ exporting: false, exportingCount: 0 }); + } + }, + { useMasterKey: true } + ); } - - // Deliver to browser to download file - const element = document.createElement('a'); - const file = new Blob([csvString], { type: 'text/csv' }); - element.href = URL.createObjectURL(file); - element.download = `${className}.csv`; - document.body.appendChild(element); // Required for this to work in FireFox - element.click(); - document.body.removeChild(element); } getClassRelationColumns(className) { @@ -1804,8 +1864,10 @@ class Browser extends DashboardView { this.confirmExportSelectedRows(this.state.rowsToExport)} + onConfirm={(type, indentation) => this.confirmExportSelectedRows(this.state.rowsToExport, type, indentation)} /> ); } @@ -1822,6 +1884,11 @@ class Browser extends DashboardView { ); } + else if (this.state.exporting) { + notification = ( + + ); + } return (
diff --git a/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js index 92f2ce50bf..0478c56d71 100644 --- a/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js +++ b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js @@ -5,36 +5,94 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import Modal from 'components/Modal/Modal.react'; -import React from 'react'; +import Modal from 'components/Modal/Modal.react'; +import React from 'react'; +import Dropdown from 'components/Dropdown/Dropdown.react'; +import Field from 'components/Field/Field.react'; +import Label from 'components/Label/Label.react'; +import Option from 'components/Dropdown/Option.react'; +import Toggle from 'components/Toggle/Toggle.react'; +import TextInput from 'components/TextInput/TextInput.react'; +import styles from 'dashboard/Data/Browser/ExportSelectedRowsDialog.scss'; export default class ExportSelectedRowsDialog extends React.Component { constructor() { super(); this.state = { - confirmation: '' + confirmation: '', + exportType: '.csv', + indentation: true, }; } valid() { + if (!this.props.selection['*']) { + return true; + } + if (this.state.confirmation !== 'export all') { + return false; + } return true; } + formatBytes(bytes) { + if (!+bytes) return '0 Bytes' + + const k = 1024 + const decimals = 2 + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}` +} + + render() { let selectionLength = Object.keys(this.props.selection).length; + const fileSize = new TextEncoder().encode(JSON.stringify(this.props.data, null, this.state.exportType === '.json' && this.state.indentation ? 2 : null)).length / this.props.data.length return ( - {} + onConfirm={() => this.props.onConfirm(this.state.exportType, this.state.indentation)}> + {this.props.selection['*'] &&
+
+ } + } + input={ + this.setState({ exportType })}> + + + + } /> + {this.state.exportType === '.json' && } + input={ {this.setState({indentation})}} />} /> + } + {this.props.selection['*'] && + } + input={ + this.setState({ confirmation })} /> + } /> + }
); } diff --git a/src/dashboard/Data/Browser/ExportSelectedRowsDialog.scss b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.scss new file mode 100644 index 0000000000..2ced8f2386 --- /dev/null +++ b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.scss @@ -0,0 +1,9 @@ +.row { + display: block; + position: relative; + height: 100px; + border-bottom: 1px solid #e0e0e1; +} +.label { + line-height: 16px; +} From 181be84d10b4932782189556b07b8c46ffa34f6a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Jan 2023 13:23:07 +0000 Subject: [PATCH 11/15] chore(release): 5.1.0-alpha.5 [skip ci] # [5.1.0-alpha.5](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2023-01-25) ### Features * Add export all rows of a class and export in JSON format ([#2361](https://github.com/ParsePlatform/parse-dashboard/issues/2361)) ([9eb36a1](https://github.com/ParsePlatform/parse-dashboard/commit/9eb36a183b8b337960f6e8563ad686958001a22b)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 71cefa1aab..039b9f3249 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.5](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2023-01-25) + + +### Features + +* Add export all rows of a class and export in JSON format ([#2361](https://github.com/ParsePlatform/parse-dashboard/issues/2361)) ([9eb36a1](https://github.com/ParsePlatform/parse-dashboard/commit/9eb36a183b8b337960f6e8563ad686958001a22b)) + # [5.1.0-alpha.4](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2023-01-25) diff --git a/package-lock.json b/package-lock.json index e53581a3d7..bf066dfc14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.4", + "version": "5.1.0-alpha.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d187e78fa1..c1b3ded8af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.4", + "version": "5.1.0-alpha.5", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From 2e650679d410af14731fdd29879f8eadf35ef59e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:26:27 +0100 Subject: [PATCH 12/15] refactor: Bump ua-parser-js from 0.7.28 to 0.7.33 (#2372) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf066dfc14..af04217c80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18427,9 +18427,9 @@ "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==" }, "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==" }, "uc.micro": { "version": "1.0.6", From 0db6f5559f9b7bb1f5a282c6182810ca89945032 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Jan 2023 06:03:30 +1100 Subject: [PATCH 13/15] fix: Navigation to page fails if user re-login is required (#2369) --- Parse-Dashboard/Authentication.js | 16 +++++++++++----- Parse-Dashboard/app.js | 5 +++-- src/login/Login.js | 10 +++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Parse-Dashboard/Authentication.js b/Parse-Dashboard/Authentication.js index 2f003c3c49..53f2b05b17 100644 --- a/Parse-Dashboard/Authentication.js +++ b/Parse-Dashboard/Authentication.js @@ -67,11 +67,17 @@ function initialize(app, options) { app.post('/login', csrf(), - passport.authenticate('local', { - successRedirect: `${self.mountPath}apps`, - failureRedirect: `${self.mountPath}login`, - failureFlash : true - }) + (req,res,next) => { + let redirect = 'apps'; + if (req.body.redirect) { + redirect = req.body.redirect.charAt(0) === '/' ? req.body.redirect.substring(1) : req.body.redirect + } + return passport.authenticate('local', { + successRedirect: `${self.mountPath}${redirect}`, + failureRedirect: `${self.mountPath}login${req.body.redirect ? `?redirect=${req.body.redirect}` : ''}`, + failureFlash : true + })(req, res, next) + }, ); app.get('/logout', function(req, res){ diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index ed03d51f70..56ac3146f0 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -173,8 +173,9 @@ module.exports = function(config, options) { } app.get('/login', csrf(), function(req, res) { + const redirectURL = req.url.includes('?redirect=') && req.url.split('?redirect=')[1]; if (!users || (req.user && req.user.isAuthenticated)) { - return res.redirect(`${mountPath}apps`); + return res.redirect(`${mountPath}${redirectURL || 'apps'}`); } let errors = req.flash('error'); @@ -206,7 +207,7 @@ module.exports = function(config, options) { // For every other request, go to index.html. Let client-side handle the rest. app.get('/*', function(req, res) { if (users && (!req.user || !req.user.isAuthenticated)) { - return res.redirect(`${mountPath}login`); + return res.redirect(`${mountPath}login?redirect=${req.url.replace('/login', '')}`); } if (users && req.user && req.user.matchingUsername ) { res.append('username', req.user.matchingUsername); diff --git a/src/login/Login.js b/src/login/Login.js index 79f4de1b23..967da8b57e 100644 --- a/src/login/Login.js +++ b/src/login/Login.js @@ -28,10 +28,13 @@ export default class Login extends React.Component { } } + const url = new URL(window.location); + const redirect = url.searchParams.get('redirect'); this.state = { forgot: false, username: sessionStorage.getItem('username') || '', - password: sessionStorage.getItem('password') || '' + password: sessionStorage.getItem('password') || '', + redirect }; sessionStorage.clear(); setBasePath(props.path); @@ -106,6 +109,11 @@ export default class Login extends React.Component { ref={this.inputRefPass} /> } /> + {this.state.redirect && } { this.errors && this.errors.includes('one-time') ? Date: Wed, 25 Jan 2023 19:05:29 +0000 Subject: [PATCH 14/15] chore(release): 5.1.0-alpha.6 [skip ci] # [5.1.0-alpha.6](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2023-01-25) ### Bug Fixes * Navigation to page fails if user re-login is required ([#2369](https://github.com/ParsePlatform/parse-dashboard/issues/2369)) ([0db6f55](https://github.com/ParsePlatform/parse-dashboard/commit/0db6f5559f9b7bb1f5a282c6182810ca89945032)) --- changelogs/CHANGELOG_alpha.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 039b9f3249..7249ba15e1 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [5.1.0-alpha.6](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2023-01-25) + + +### Bug Fixes + +* Navigation to page fails if user re-login is required ([#2369](https://github.com/ParsePlatform/parse-dashboard/issues/2369)) ([0db6f55](https://github.com/ParsePlatform/parse-dashboard/commit/0db6f5559f9b7bb1f5a282c6182810ca89945032)) + # [5.1.0-alpha.5](https://github.com/ParsePlatform/parse-dashboard/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2023-01-25) diff --git a/package-lock.json b/package-lock.json index af04217c80..e3b64f3930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.5", + "version": "5.1.0-alpha.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c1b3ded8af..7cb8027346 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "5.1.0-alpha.5", + "version": "5.1.0-alpha.6", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From e974022bee805106a683897f456c0c98a720fcbf Mon Sep 17 00:00:00 2001 From: Shruti Chaturvedi <66940685+ShrutiC-git@users.noreply.github.com> Date: Thu, 26 Jan 2023 20:43:04 +0530 Subject: [PATCH 15/15] Update index.html --- nginx-uffizzi/html/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/nginx-uffizzi/html/index.html b/nginx-uffizzi/html/index.html index 1f624f6a33..116a29ae7b 100644 --- a/nginx-uffizzi/html/index.html +++ b/nginx-uffizzi/html/index.html @@ -14,7 +14,6 @@

Endpoint:

Click to Visit Parse Dashboard -