diff --git a/CHANGELOG.md b/CHANGELOG.md index d338a61c57..d7472efa2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,19 +15,6 @@ Details: - Purpose: official release - Suitable environment: production -## ⚠️ [Beta Releases][log_beta] - -These are releases that are pretty stable, but may have still some bugs to be fixed before official release. - -> ### “Please try out, we’d love your feedback!” - -Details: -- Stability: *pretty stable* -- NPM channel: `@beta` -- Branch: [beta][branch_beta] -- Purpose: feature maturation -- Suitable environment: development - ## 🔥 [Alpha Releases][log_alpha] > ### “If you are curious to see what's next!” @@ -43,8 +30,6 @@ Details: [log_release]: https://github.com/parse-community/parse-dashboard/blob/release/changelogs/CHANGELOG_release.md -[log_beta]: https://github.com/parse-community/parse-dashboard/blob/beta/changelogs/CHANGELOG_beta.md [log_alpha]: https://github.com/parse-community/parse-dashboard/blob/alpha/changelogs/CHANGELOG_alpha.md [branch_release]: https://github.com/parse-community/parse-dashboard/tree/release -[branch_beta]: https://github.com/parse-community/parse-dashboard/tree/beta [branch_alpha]: https://github.com/parse-community/parse-dashboard/tree/alpha diff --git a/Dockerfile b/Dockerfile index 1570cf871f..c131f9aac3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ############################################################ # Build stage ############################################################ -FROM node:20.17.0-alpine3.20 AS build +FROM node:20.18.2-alpine3.20 AS build RUN apk --no-cache add git WORKDIR /src @@ -27,7 +27,7 @@ RUN npm run prepare && npm run build ############################################################ # Release stage ############################################################ -FROM node:20.17.0-alpine3.20 AS release +FROM node:20.18.2-alpine3.20 AS release WORKDIR /src # Copy production node_modules diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 5ec6cc26b9..5e3ecd4ae7 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -5,6 +5,7 @@ const packageJson = require('package-json'); const csrf = require('csurf'); const Authentication = require('./Authentication.js'); const fs = require('fs'); +const ConfigKeyCache = require('./configKeyCache.js'); const currentVersionFeatures = require('../package.json').parseDashboardFeatures; @@ -80,11 +81,11 @@ module.exports = function(config, options) { }); // Serve the configuration. - app.get('/parse-dashboard-config.json', function(req, res) { + app.get('/parse-dashboard-config.json', async (req, res) => { const apps = config.apps.map((app) => Object.assign({}, app)); // make a copy const response = { - apps: apps, - newFeaturesInLatestVersion: newFeaturesInLatestVersion, + apps, + newFeaturesInLatestVersion, }; //Based on advice from Doug Wilson here: @@ -119,20 +120,31 @@ module.exports = function(config, options) { return app; }); } - if (successfulAuth) { if (appsUserHasAccess) { - // Restric access to apps defined in user dictionary - // If they didn't supply any app id, user will access all apps - response.apps = response.apps.filter(function (app) { - return appsUserHasAccess.find(appUserHasAccess => { - const isSame = app.appId === appUserHasAccess.appId; - if (isSame && appUserHasAccess.readOnly) { + const processedApps = await Promise.all( + response.apps.map(async (app) => { + const matchingAccess = appsUserHasAccess.find( + (access) => access.appId === app.appId + ); + + if (!matchingAccess) { + return null; + } + + if (matchingAccess.readOnly) { app.masterKey = app.readOnlyMasterKey; } - return isSame; + + if (typeof app.masterKey === 'function') { + app.masterKey = await ConfigKeyCache.get(app.appId, 'masterKey', app.masterKeyTtl, app.masterKey); + } + + return app; }) - }); + ); + + response.apps = processedApps.filter((app) => app !== null); } // They provided correct auth return res.json(response); @@ -147,6 +159,14 @@ module.exports = function(config, options) { //(ie. didn't supply usernames and passwords) if (requestIsLocal || options.dev) { //Allow no-auth access on localhost only, if they have configured the dashboard to not need auth + await Promise.all( + response.apps.map(async (app) => { + if (typeof app.masterKey === 'function') { + app.masterKey = await ConfigKeyCache.get(app.appId, 'masterKey', app.masterKeyTtl, app.masterKey); + } + }) + ); + return res.json(response); } //We shouldn't get here. Fail closed. diff --git a/Parse-Dashboard/configKeyCache.js b/Parse-Dashboard/configKeyCache.js new file mode 100644 index 0000000000..3b8ad9b31f --- /dev/null +++ b/Parse-Dashboard/configKeyCache.js @@ -0,0 +1,22 @@ +class KeyCache { + constructor() { + this.cache = {}; + } + + async get(appId, key, ttl, callback) { + key = `${appId}:${key}`; + const cached = this.cache[key]; + if (cached && cached.expiry > Date.now()) { + return cached.value; + } + + const value = await Promise.resolve(callback()); + this.cache[key] = { + value, + expiry: Date.now() + ttl, + }; + return value; + } +} + +module.exports = new KeyCache(); diff --git a/Parse-Dashboard/index.js b/Parse-Dashboard/index.js index 6217df3e95..f472ec0844 100644 --- a/Parse-Dashboard/index.js +++ b/Parse-Dashboard/index.js @@ -13,6 +13,7 @@ const startServer = require('./server'); 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('--masterKeyTtl [masterKeyTtl]', 'the master key ttl 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('--graphQLServerURL [graphQLServerURL]', 'the GraphQL server url of the app you would like to manage.'); program.option('--dev', 'Enable development mode. This will disable authentication and allow non HTTPS connections. DO NOT ENABLE IN PRODUCTION SERVERS'); diff --git a/README.md b/README.md index 7f41abcae3..ef45a68adf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ --- [![Build Status](https://github.com/parse-community/parse-dashboard/workflows/ci/badge.svg?branch=alpha)](https://github.com/parse-community/parse-dashboard/actions?query=workflow%3Aci+branch%3Aalpha) -[![Build Status](https://github.com/parse-community/parse-dashboard/workflows/ci/badge.svg?branch=beta)](https://github.com/parse-community/parse-dashboard/actions?query=workflow%3Aci+branch%3Abeta) [![Build Status](https://github.com/parse-community/parse-dashboard/workflows/ci/badge.svg?branch=release)](https://github.com/parse-community/parse-dashboard/actions?query=workflow%3Aci+branch%3Arelease) [![Snyk Badge](https://snyk.io/test/github/parse-community/parse-dashboard/badge.svg)](https://snyk.io/test/github/parse-community/parse-dashboard) @@ -11,7 +10,6 @@ [![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases) [![npm latest version](https://img.shields.io/npm/v/parse-dashboard/latest.svg)](https://www.npmjs.com/package/parse-dashboard) -[![npm beta version](https://img.shields.io/npm/v/parse-dashboard/beta.svg)](https://www.npmjs.com/package/parse-dashboard) [![npm alpha version](https://img.shields.io/npm/v/parse-dashboard/alpha.svg)](https://www.npmjs.com/package/parse-dashboard) [![Backers on Open Collective](https://opencollective.com/parse-server/backers/badge.svg)][open-collective-link] @@ -71,6 +69,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Video Item](#video-item) - [Audio Item](#audio-item) - [Button Item](#button-item) + - [Panel Item](#panel-item) - [Browse as User](#browse-as-user) - [Change Pointer Key](#change-pointer-key) - [Limitations](#limitations) @@ -130,6 +129,11 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js | Parameter | Type | Optional | Default | Example | Description | |----------------------------------------|---------------------|----------|---------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | `apps` | Array<Object> | no | - | `[{ ... }, { ... }]` | The apps that are configured for the dashboard. | +| `apps.appId` | String | yes | - | `"myAppId"` | The Application ID for your Parse Server instance. | +| `apps.masterKey` | String \| Function | yes | - | `"exampleMasterKey"`, `() => "exampleMasterKey"` | The master key for full access to Parse Server. It can be provided directly as a String or as a Function returning a String. | +| `apps.masterKeyTtl` | Number | no | - | `3600` | Time-to-live (TTL) for the master key in seconds. This defines how long the master key is cached before the `masterKey` function is re-triggered. | +| `apps.serverURL` | String | yes | - | `"http://localhost:1337/parse"` | The URL where your Parse Server is running. | +| `apps.appName` | String | no | - | `"MyApp"` | The display name of the app in the dashboard. | | `infoPanel` | Array<Object> | yes | - | `[{ ... }, { ... }]` | The [info panel](#info-panel) configuration. | | `infoPanel[*].title` | String | no | - | `User Details` | The panel title. | | `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. | @@ -929,12 +933,13 @@ Example: A text item that consists of a key and a value. The value can optionally be linked to a URL. -| Parameter | Value | Optional | Description | -|-----------|--------|----------|-----------------------------------------------------------------------------------| -| `type` | String | No | Must be `"keyValue"`. | -| `key` | String | No | The key text to display. | -| `value` | String | No | The value text to display. | -| `url` | String | Yes | The URL that will be opened in a new browser tab when clicking on the value text. | +| Parameter | Value | Default | Optional | Description | +|-----------------|---------|-------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `type` | String | - | No | Must be `"keyValue"`. | +| `key` | String | - | No | The key text to display. | +| `value` | String | - | No | The value text to display. | +| `url` | String | `undefined` | Yes | The URL that will be opened in a new browser tab when clicking on the value text. It can be set to an absolute URL or a relative URL in which case the base URL is `:////`. | +| `isRelativeUrl` | Boolean | `false` | Yes | Set this to `true` when linking to another dashboard page, in which case the base URL for the relative URL will be `:////apps//`. | Examples: @@ -951,7 +956,33 @@ Examples: "type": "keyValue", "key": "Last purchase ID", "value": "123", - "url": "https://example.com/purchaseDetails?purchaseId=012345" + "url": "https://example.com/purchaseDetails?purchaseId=123" +} +``` + +```json +{ + "type": "keyValue", + "key": "Purchase", + "value": "123", + "url": "browser/Purchase", + "isRelativeUrl": true +} +``` + +To navigate to a specific object using a relative URL, the query parameters must be URL encoded: + +```js +const objectId = 'abc123'; +const className = 'Purchase'; +const query = [{ field: 'objectId', constraint: 'eq', compareTo: objectId }]; +const url = `browser/Purchase?filters=${JSON.stringify(query)}`; +const item = { + type: 'keyValue', + key: 'Purchase', + value: objectId, + url, + isRelativeUrl: true } ``` @@ -1082,6 +1113,26 @@ Example: } ``` +#### Panel Item + +A sub-panel whose data is loaded on-demand by expanding the item. + +| Parameter | Value | Optional | Description | +|---------------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `type` | String | No | Must be `"infoPanel"`. | +| `title` | String | No | The title to display in the expandable headline. | +| `cloudCodeFunction` | String | No | The Cloud Code Function to call which receives the selected object in the data browser and returns the response to be displayed in the sub-panel. | + +Example: + +```json +{ + "type": "panel", + "title": "Purchase History", + "cloudCodeFunction": "getUserPurchaseHistory" +} +``` + ## Browse as User ▶️ *Core > Browser > Browse* diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 1d66232a96..2f0007bdb2 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,101 @@ +# [6.0.0-alpha.30](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.29...6.0.0-alpha.30) (2025-03-03) + + +### Features + +* Add cell selection in data browser on space bar touch down ([#2661](https://github.com/ParsePlatform/parse-dashboard/issues/2661)) ([9d623a9](https://github.com/ParsePlatform/parse-dashboard/commit/9d623a97a4e9ff9692f72191a33441a22fb6956e)) + +# [6.0.0-alpha.29](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.28...6.0.0-alpha.29) (2025-02-14) + + +### Features + +* Add dynamic master key by allowing to set option `masterKey` to a function ([#2655](https://github.com/ParsePlatform/parse-dashboard/issues/2655)) ([9025ed0](https://github.com/ParsePlatform/parse-dashboard/commit/9025ed07b5e7fd801a6ec56c71a12299b2d57279)) + +# [6.0.0-alpha.28](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.27...6.0.0-alpha.28) (2025-02-01) + + +### Bug Fixes + +* Info panel item `panel` calls Cloud Code with parameter `objectId` instead of `Parse.Object` and without `masterKey` ([#2649](https://github.com/ParsePlatform/parse-dashboard/issues/2649)) ([884ff70](https://github.com/ParsePlatform/parse-dashboard/commit/884ff70567e372ff676297b6fb7856fbb7b71cbb)) + +# [6.0.0-alpha.27](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.26...6.0.0-alpha.27) (2025-02-01) + + +### Bug Fixes + +* Security upgrade node from 20.17.0-alpine3.20 to 20.18.2-alpine3.20 ([#2647](https://github.com/ParsePlatform/parse-dashboard/issues/2647)) ([44df723](https://github.com/ParsePlatform/parse-dashboard/commit/44df723b56636607f44c16f2ca24e81e0e17dfb3)) + +# [6.0.0-alpha.26](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.25...6.0.0-alpha.26) (2025-01-31) + + +### Features + +* Add info panel `keyValue` item parameter `isRelativeUrl` to link to dashboard pages ([#2646](https://github.com/ParsePlatform/parse-dashboard/issues/2646)) ([6389fc6](https://github.com/ParsePlatform/parse-dashboard/commit/6389fc6097a76dc95e3cbcdab56c8d1f96909d97)) + +# [6.0.0-alpha.25](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.24...6.0.0-alpha.25) (2025-01-30) + + +### Features + +* Add info panel item `panel` to load and display data on demand ([#2622](https://github.com/ParsePlatform/parse-dashboard/issues/2622)) ([8e5741d](https://github.com/ParsePlatform/parse-dashboard/commit/8e5741d73b5a8c4fcb5d4248de1bdcd7bd957ee8)) + +# [6.0.0-alpha.24](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.23...6.0.0-alpha.24) (2025-01-29) + + +### Bug Fixes + +* Info panel Cloud Code call sends `objectId` instead of `Parse.Object` ([#2643](https://github.com/ParsePlatform/parse-dashboard/issues/2643)) ([a4bcabc](https://github.com/ParsePlatform/parse-dashboard/commit/a4bcabc5c5eaf07bc9eed11814c19901e1d310da)) + +# [6.0.0-alpha.23](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.22...6.0.0-alpha.23) (2025-01-29) + + +### Bug Fixes + +* Info panel Cloud Code call is unauthenticated without using master key ([#2641](https://github.com/ParsePlatform/parse-dashboard/issues/2641)) ([e879e4f](https://github.com/ParsePlatform/parse-dashboard/commit/e879e4f541dc0aa3e23afe6606ee7df9ba22b63a)) + +# [6.0.0-alpha.22](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.21...6.0.0-alpha.22) (2025-01-29) + + +### Bug Fixes + +* Info panel not configurable via `new ParseDashboard()` when running as express middleware ([#2639](https://github.com/ParsePlatform/parse-dashboard/issues/2639)) ([a9b8cd4](https://github.com/ParsePlatform/parse-dashboard/commit/a9b8cd4a7228837cbb462a45e39b01494852f347)) + +# [6.0.0-alpha.21](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.20...6.0.0-alpha.21) (2025-01-24) + + +### Bug Fixes + +* Info panel not showing when some apps miss infoPanel config ([#2627](https://github.com/ParsePlatform/parse-dashboard/issues/2627)) ([539e883](https://github.com/ParsePlatform/parse-dashboard/commit/539e88348721bc100a80ae00de81a921bc2c53d4)) + +# [6.0.0-alpha.20](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.19...6.0.0-alpha.20) (2024-11-19) + + +### Bug Fixes + +* Security upgrade cross-spawn from 7.0.3 to 7.0.6 ([#2629](https://github.com/ParsePlatform/parse-dashboard/issues/2629)) ([47a43e0](https://github.com/ParsePlatform/parse-dashboard/commit/47a43e0ac5d55fc9e214079895f71af7c7e3c350)) + +# [6.0.0-alpha.19](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.18...6.0.0-alpha.19) (2024-10-16) + + +### Bug Fixes + +* Security upgrade ws, parse and puppeteer ([#2618](https://github.com/ParsePlatform/parse-dashboard/issues/2618)) ([bab71dc](https://github.com/ParsePlatform/parse-dashboard/commit/bab71dc57195efa62518127de842edd5902603de)) + +# [6.0.0-alpha.18](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.17...6.0.0-alpha.18) (2024-10-16) + + +### Bug Fixes + +* Node 22 support missing in package.json ([#2617](https://github.com/ParsePlatform/parse-dashboard/issues/2617)) ([8c07284](https://github.com/ParsePlatform/parse-dashboard/commit/8c07284cd571c69426c3f080c2698b0624fd4ec4)) + +# [6.0.0-alpha.17](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.16...6.0.0-alpha.17) (2024-10-09) + + +### Bug Fixes + +* Security upgrade express from 4.21.0 to 4.21.1 ([#2607](https://github.com/ParsePlatform/parse-dashboard/issues/2607)) ([54bf0af](https://github.com/ParsePlatform/parse-dashboard/commit/54bf0afa0691e448b7ec20395753468e047e1fd1)) + # [6.0.0-alpha.16](https://github.com/ParsePlatform/parse-dashboard/compare/6.0.0-alpha.15...6.0.0-alpha.16) (2024-10-07) diff --git a/package-lock.json b/package-lock.json index c0f4b975de..0947717ece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-dashboard", - "version": "6.0.0", + "version": "6.0.0-alpha.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "parse-dashboard", - "version": "6.0.0", + "version": "6.0.0-alpha.30", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@babel/runtime": "7.20.13", @@ -19,7 +19,7 @@ "copy-to-clipboard": "3.3.2", "core-js": "3.28.0", "csurf": "1.11.0", - "express": "4.21.0", + "express": "4.21.2", "graphiql": "2.0.8", "graphql": "16.8.1", "immutable": "4.1.0", @@ -28,7 +28,7 @@ "js-beautify": "1.14.10", "otpauth": "8.0.3", "package-json": "7.0.0", - "parse": "3.4.2", + "parse": "3.5.1", "passport": "0.5.3", "passport-local": "1.0.0", "prismjs": "1.29.0", @@ -80,7 +80,7 @@ "marked": "4.1.1", "null-loader": "4.0.1", "prettier": "2.8.8", - "puppeteer": "22.6.1", + "puppeteer": "22.15.0", "react-test-renderer": "16.13.1", "request": "2.88.2", "request-promise": "4.2.5", @@ -96,7 +96,7 @@ "yaml": "1.10.0" }, "engines": { - "node": ">=18.0.0 <21" + "node": ">=18.0.0 <21 || >=22.9.0 <23" } }, "node_modules/@actions/core": { @@ -5047,19 +5047,19 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.0.tgz", - "integrity": "sha512-MC7LxpcBtdfTbzwARXIkqGZ1Osn3nnZJlm+i0+VqHl72t//Xwl9wICrXT8BwtgC6s1xJNHsxOpvzISUqe92+sw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.4.0", - "semver": "7.6.0", - "tar-fs": "3.0.5", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.2" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" @@ -5069,13 +5069,10 @@ } }, "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -6940,9 +6937,9 @@ } }, "node_modules/ast-types/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", "dev": true }, "node_modules/async": { @@ -7719,14 +7716,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.14.tgz", - "integrity": "sha512-zm4mX61/U4KXs+S/0WIBHpOWqtpW6FPv1i7n4UZqDDc5LOQ9/Y1MAnB95nO7i/lFFuijLjpe1XMdNcqDqwlH5w==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", - "zod": "3.22.4" + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" @@ -8425,9 +8422,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8641,12 +8638,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -9119,9 +9116,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1262051", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", - "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true }, "node_modules/diff-match-patch": { @@ -10320,17 +10317,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", - "license": "MIT", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -10344,7 +10340,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -10359,12 +10355,17 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -15889,9 +15890,9 @@ "dev": true }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -19457,16 +19458,16 @@ } }, "node_modules/parse": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/parse/-/parse-3.4.2.tgz", - "integrity": "sha512-Ruehcp/S7eB3A0lDG5eAPvZHa5pABCbUR+lMJL2gUNKJLZNcD9/s3RL255PwI5jTqa+TCJ7MdPqobUplouN1pQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/parse/-/parse-3.5.1.tgz", + "integrity": "sha512-uxkLVNfbqgZ/pStg/jIQGh09tOUkPZorzDIqz9vSFjZ3iIZzgvNk6VBbjXjcMWsR8LyFPOR1ROR6/y9Dedn3/w==", "dependencies": { - "@babel/runtime": "7.17.9", + "@babel/runtime": "7.18.0", "@babel/runtime-corejs3": "7.17.8", "idb-keyval": "6.0.3", "react-native-crypto-js": "1.0.0", "uuid": "3.4.0", - "ws": "7.5.1", + "ws": "8.6.0", "xmlhttprequest": "1.8.0" }, "optionalDependencies": { @@ -19501,9 +19502,9 @@ } }, "node_modules/parse/node_modules/@babel/runtime": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", - "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.0.tgz", + "integrity": "sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -19524,11 +19525,11 @@ } }, "node_modules/parse/node_modules/ws": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", - "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", + "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", @@ -19666,10 +19667,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "license": "MIT" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -20255,17 +20255,16 @@ } }, "node_modules/puppeteer": { - "version": "22.6.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.6.1.tgz", - "integrity": "sha512-736QHNKtPD4tPeFbIn73E4l0CWsLzvRFlm0JsLG/VsyM8Eh0FRFNmMp+M3+GSMwdmYxqOVpTgzB6VQDxWxu8xQ==", - "deprecated": "< 22.8.2 is no longer supported", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz", + "integrity": "sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "2.2.0", - "cosmiconfig": "9.0.0", - "devtools-protocol": "0.0.1262051", - "puppeteer-core": "22.6.1" + "@puppeteer/browsers": "2.3.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.15.0" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" @@ -20275,16 +20274,16 @@ } }, "node_modules/puppeteer-core": { - "version": "22.6.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.6.1.tgz", - "integrity": "sha512-rShSd0xtyDSEJYys5nnzQnnwtrafQWg/lWCppyjZIIbYadWP8B1u0XJD/Oe+Xgw8v1hLHX0loNoA0ItRmNLnBg==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "dependencies": { - "@puppeteer/browsers": "2.2.0", - "chromium-bidi": "0.5.14", - "debug": "4.3.4", - "devtools-protocol": "0.0.1262051", - "ws": "8.16.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { "node": ">=18" @@ -21971,12 +21970,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -22742,9 +22735,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", - "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { "pump": "^3.0.0", @@ -24021,9 +24014,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -24196,9 +24189,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 69cc8b8546..dacbd79453 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "6.0.0", + "version": "6.0.0-alpha.30", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" @@ -45,7 +45,7 @@ "copy-to-clipboard": "3.3.2", "core-js": "3.28.0", "csurf": "1.11.0", - "express": "4.21.0", + "express": "4.21.2", "graphiql": "2.0.8", "graphql": "16.8.1", "immutable": "4.1.0", @@ -54,7 +54,7 @@ "js-beautify": "1.14.10", "otpauth": "8.0.3", "package-json": "7.0.0", - "parse": "3.4.2", + "parse": "3.5.1", "passport": "0.5.3", "passport-local": "1.0.0", "prismjs": "1.29.0", @@ -103,7 +103,7 @@ "marked": "4.1.1", "null-loader": "4.0.1", "prettier": "2.8.8", - "puppeteer": "22.6.1", + "puppeteer": "22.15.0", "react-test-renderer": "16.13.1", "request": "2.88.2", "request-promise": "4.2.5", @@ -139,7 +139,7 @@ "parse-dashboard": "./bin/parse-dashboard" }, "engines": { - "node": ">=18.0.0 <21" + "node": ">=18.0.0 <21 || >=22.9.0 <23" }, "main": "Parse-Dashboard/app.js", "jest": { diff --git a/release.config.js b/release.config.js index 082f461781..50d2877de2 100644 --- a/release.config.js +++ b/release.config.js @@ -92,8 +92,9 @@ async function config() { '@saithodev/semantic-release-backmerge', { 'branches': [ - { from: 'beta', to: 'alpha' }, - { from: 'release', to: 'beta' }, + // { from: 'beta', to: 'alpha' }, + // { from: 'release', to: 'beta' }, + { from: 'release', to: 'alpha' }, ] } ], diff --git a/src/components/AggregationPanel/AggregationPanel.js b/src/components/AggregationPanel/AggregationPanel.js index 5cd5a0440b..1b64c0adb0 100644 --- a/src/components/AggregationPanel/AggregationPanel.js +++ b/src/components/AggregationPanel/AggregationPanel.js @@ -1,5 +1,6 @@ import LoaderDots from 'components/LoaderDots/LoaderDots.react'; -import React, { useEffect, useMemo } from 'react'; +import Parse from 'parse'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styles from './AggregationPanel.scss'; import { AudioElement, @@ -19,8 +20,16 @@ const AggregationPanel = ({ errorAggregatedData, showNote, setSelectedObjectId, - selectedObjectId + selectedObjectId, + className, + appName, + depth = 0, + cloudCodeFunction = null, + panelTitle = null, }) => { + const [isExpanded, setIsExpanded] = useState(false); + const [nestedData, setNestedData] = useState(null); + const [isLoadingNested, setIsLoadingNested] = useState(false); useEffect(() => { if (Object.keys(errorAggregatedData).length !== 0) { @@ -30,54 +39,155 @@ const AggregationPanel = ({ }, [errorAggregatedData, setSelectedObjectId, setErrorAggregatedData]); const isLoading = useMemo(() => - selectedObjectId && isLoadingCloudFunction && showAggregatedData, - [selectedObjectId, isLoadingCloudFunction, showAggregatedData] + depth === 0 && selectedObjectId && isLoadingCloudFunction && showAggregatedData, + [depth, selectedObjectId, isLoadingCloudFunction, showAggregatedData] ); const shouldShowAggregatedData = useMemo(() => - selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0, [selectedObjectId, showAggregatedData, data, errorAggregatedData] + depth === 0 + ? (selectedObjectId && showAggregatedData && Object.keys(data).length !== 0 && Object.keys(errorAggregatedData).length === 0) + : true, + [depth, selectedObjectId, showAggregatedData, data, errorAggregatedData] ); + const fetchNestedData = useCallback(async () => { + setIsLoadingNested(true); + try { + const params = { + object: Parse.Object.extend(className).createWithoutData(selectedObjectId).toPointer(), + }; + const options = { + useMasterKey: true, + }; + const result = await Parse.Cloud.run(cloudCodeFunction, params, options); + if (result?.panel?.segments) { + setNestedData(result); + } else { + const errorMsg = 'Improper JSON format'; + showNote(errorMsg, true); + } + } catch (error) { + const errorMsg = error.message; + showNote(errorMsg, true); + } finally { + setIsLoadingNested(false); + } + }, [cloudCodeFunction, selectedObjectId, showNote]); + + const handleToggle = useCallback(async () => { + if (!isExpanded && !nestedData && cloudCodeFunction) { + fetchNestedData(); + } + setIsExpanded(prev => !prev); + }, [isExpanded, nestedData, cloudCodeFunction, fetchNestedData]); + + const handleRefresh = useCallback(() => { + setNestedData(null); + setIsExpanded(false); + fetchNestedData(); + }, [fetchNestedData]); + + const renderSegmentContent = (segment, index) => ( +
+

{segment.title}

+
+ {segment.items.map((item, idx) => { + switch (item.type) { + case 'text': + return ; + case 'keyValue': + return ; + case 'table': + return ; + case 'image': + return ; + case 'video': + return ; + case 'audio': + return ; + case 'button': + return ; + case 'panel': + return ( +
+ +
+ ); + default: + return null; + } + })} +
+
+ ); + + if (depth > 0) { + return ( +
+
+ {panelTitle} +
+ {isExpanded && ( + + + )} + {isExpanded ? '▼' : '▲'} +
+
+ {isExpanded && ( +
+ {isLoadingNested ? ( +
+ +
+ ) : ( + nestedData && nestedData.panel.segments.map((segment, index) => + renderSegmentContent(segment, index) + ) + )} +
+ )} +
+ ); + } + return ( - <> +
{isLoading ? (
) : shouldShowAggregatedData ? ( - data.panel.segments.map((segment, index) => ( -
-

{segment.title}

-
- {segment.items.map((item, idx) => { - switch (item.type) { - case 'text': - return ; - case 'keyValue': - return ; - case 'table': - return ; - case 'image': - return ; - case 'video': - return ; - case 'audio': - return ; - case 'button': - return ; - default: - return null; - } - })} -
-
- )) +
+ {data.panel.segments.map((segment, index) => + renderSegmentContent(segment, index) + )} +
) : ( -
- No object selected. +
+ No object selected.
)} - +
); }; diff --git a/src/components/AggregationPanel/AggregationPanel.scss b/src/components/AggregationPanel/AggregationPanel.scss index 2bfda14147..58edd2a41e 100644 --- a/src/components/AggregationPanel/AggregationPanel.scss +++ b/src/components/AggregationPanel/AggregationPanel.scss @@ -11,14 +11,11 @@ .segmentItems { font-size: 14px; - padding-left: 10px; - padding-right: 10px; - padding-top: 6px; + padding: 10px; display: flex; flex-direction: column; - border-left: 1px solid #e3e3ea; gap: 10px; -} + } .keyValue { font-size: 14px; @@ -76,13 +73,14 @@ text-align: center; border-radius: 5px; font-size: 14px; + &:hover, &:focus { background-color: $darkBlue; } } -.loading{ +.loading { height: 100%; display: flex; flex-direction: column; @@ -96,4 +94,69 @@ top: 50%; left: 50%; @include transform(translate(-50%, -50%)); +} + +.nestedPanel { + margin-right: -10px; + transition: all 0.3s ease; +} + +.nestedPanelHeader { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + border-left: 1px solid transparent; + background-color: $blue; + color: $white; + cursor: pointer; + padding-right: 4px; + + &.expanded { + border-left-color: #e3e3ea; + } + } + +.expandButton { + display: inline; + padding: 8px; + font-weight: 500; + + &.expanded { + font-weight: 600; + } +} + +.refreshButton { + background: none; + border: none; + padding: 4px; + cursor: pointer; + color: #666; + font-size: 16px; + + &:hover { + color: #333; + } + + &:disabled { + color: #ccc; + cursor: not-allowed; + } +} + +.nestedPanelContent { + padding: 0 0 8px 0; +} + +.loader { + display: flex; + align-items: center; + justify-content: center; +} + +.segmentContainer { + border-left: 1px solid #e3e3ea; + border-bottom: 1px solid #e3e3ea; + margin-bottom: 16px; } \ No newline at end of file diff --git a/src/components/AggregationPanel/AggregationPanelComponents.js b/src/components/AggregationPanel/AggregationPanelComponents.js index 2f876c17d0..d81ca31569 100644 --- a/src/components/AggregationPanel/AggregationPanelComponents.js +++ b/src/components/AggregationPanel/AggregationPanelComponents.js @@ -9,10 +9,10 @@ export const TextElement = ({ text }) => ( ); // Key-Value Element Component -export const KeyValueElement = ({ item }) => ( +export const KeyValueElement = ({ item, appName }) => (
{item.key}: - {item.url ? {item.value} : {item.value}} + {item.url ? {item.value} : {item.value}}
); diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 74614feb69..b794f13f61 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -294,7 +294,7 @@ export default class BrowserCell extends Component { this.props.setShowAggregatedData(true); this.props.setSelectedObjectId(this.props.objectId); if (this.props.isPanelVisible) { - this.props.callCloudFunction(this.props.objectId, this.props.className); + this.props.callCloudFunction(this.props.objectId, this.props.className, this.props.appId); } } @@ -649,7 +649,7 @@ export default class BrowserCell extends Component { isPanelVisible && ((e.shiftKey && !this.props.firstSelectedCell) || !e.shiftKey) ) { - callCloudFunction(this.props.objectId, this.props.className); + callCloudFunction(this.props.objectId, this.props.className, this.props.appId); } } handleCellClick(e, row, col); diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js index aad773e702..670cd205dc 100644 --- a/src/components/Toolbar/Toolbar.react.js +++ b/src/components/Toolbar/Toolbar.react.js @@ -15,7 +15,7 @@ import { useNavigate, useNavigationType, NavigationType } from 'react-router-dom const POPOVER_CONTENT_ID = 'toolbarStatsPopover'; -const Stats = ({ data, classwiseCloudFunctions, className }) => { +const Stats = ({ data, classwiseCloudFunctions, className, appId , appName}) => { const [selected, setSelected] = React.useState(null); const [open, setOpen] = React.useState(false); const buttonRef = React.useRef(); @@ -98,7 +98,7 @@ const Stats = ({ data, classwiseCloudFunctions, className }) => { setSelected(statsOptions[0]); }, []); - const rightMarginStyle = classwiseCloudFunctions && classwiseCloudFunctions[className] ? '120px' : 'initial'; + const rightMarginStyle = classwiseCloudFunctions && classwiseCloudFunctions[`${appId}${appName}`] && classwiseCloudFunctions[`${appId}${appName}`][className] ? '120px' : 'initial'; return ( <> @@ -140,9 +140,9 @@ const Toolbar = props => {
- {props?.selectedData?.length ? : null} + {props?.selectedData?.length ? : null}
{props.children}
- {props.classwiseCloudFunctions && props.classwiseCloudFunctions[props.className] && ( + {props.classwiseCloudFunctions && props.classwiseCloudFunctions[`${props.appId}${props.appName}`] && props.classwiseCloudFunctions[`${props.appId}${props.appName}`][props.className] && (