From dd4d87219a55db0ff36d912c2404ab8693c93914 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Mon, 10 Oct 2022 14:35:39 +0200 Subject: [PATCH 01/11] Added hotfix for race condition in Neo4j Desktop (#230) * Added hotfix for race condition in Neo4j Desktop * Bumped version number * Added check to ensure the database list doesn't get reloaded too often * Added TODOs --- .github/workflows/master-deployment.yml | 2 +- Dockerfile | 2 +- changelog.md | 4 ++-- package.json | 2 +- release-notes.md | 4 ++-- src/application/ApplicationActions.ts | 4 ++-- src/application/ApplicationReducer.ts | 6 ++++-- src/application/ApplicationSelectors.ts | 4 +++- src/application/ApplicationThunks.ts | 2 +- src/card/Card.tsx | 20 ++++++++++++-------- src/dashboard/Dashboard.tsx | 10 +++++++--- src/modal/AboutModal.tsx | 2 +- src/report/Report.tsx | 4 ---- src/settings/SettingsSelectors.ts | 14 +++++++++++--- 14 files changed, 48 insertions(+), 32 deletions(-) diff --git a/.github/workflows/master-deployment.yml b/.github/workflows/master-deployment.yml index e50686e6d..a00943219 100644 --- a/.github/workflows/master-deployment.yml +++ b/.github/workflows/master-deployment.yml @@ -78,7 +78,7 @@ jobs: context: . file: ./Dockerfile push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_USERNAME }}/neodash:2.1.8 + tags: ${{ secrets.DOCKER_HUB_USERNAME }}/neodash:latest,${{ secrets.DOCKER_HUB_USERNAME }}/neodash:2.1.9 build-npm: needs: build-test runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index 094027973..7ee58368a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,4 +38,4 @@ RUN chown -R nginx:nginx /usr/share/nginx/html/ USER nginx EXPOSE 5005 HEALTHCHECK cmd curl --fail http://localhost:5005 || exit 1 -LABEL version="2.1.8" +LABEL version="2.1.9" diff --git a/changelog.md b/changelog.md index fdd2e851d..4ddc15ac9 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -## NeoDash 2.1.8 +## NeoDash 2.1.8 & 2.1.9 New features: - Added the [Dashboard Gallery](https://neodash-gallery.graphapp.io), a live gallery of example NeoDash dashboards. - Added **Gauge Charts**, a contribution of the [BlueHound](https://github.com/zeronetworks/BlueHound) fork. @@ -10,7 +10,7 @@ Bug fixes: - Fixed issue preventing dashboards to be shared with a non-standard database name. - Fixed table chart breaking when returning a property called 'id' with a null value. - Fixed bug not allowing users to select a different database when loading/saving a dashboard. - +- **Added error handler for database list race condition in Neo4j Desktop**. ## NeoDash 2.1.6 & 2.1.7 diff --git a/package.json b/package.json index 25c14e5cf..4d9e80d7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neodash", - "version": "2.1.8", + "version": "2.1.9", "description": "NeoDash - Neo4j Dashboard Builder", "neo4jDesktop": { "apiVersion": "^1.2.0" diff --git a/release-notes.md b/release-notes.md index 80e53a503..bfb179e9c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,4 @@ -## NeoDash 2.1.8 +## NeoDash 2.1.8 & 2.1.9 New features: - Added the [Dashboard Gallery](https://neodash-gallery.graphapp.io), a live gallery of example NeoDash dashboards. - Added **Gauge Charts**, a contribution of the [BlueHound](https://github.com/zeronetworks/BlueHound) fork. @@ -10,6 +10,6 @@ Bug fixes: - Fixed issue preventing dashboards to be shared with a non-standard database name. - Fixed table chart breaking when returning a property called 'id' with a null value. - Fixed bug not allowing users to select a different database when loading/saving a dashboard. - +- **Added error handler for database list race condition in Neo4j Desktop**. For a complete version history, see the [Changelog](https://github.com/neo4j-labs/neodash/blob/master/changelog.md). diff --git a/src/application/ApplicationActions.ts b/src/application/ApplicationActions.ts index 114ad7375..8fc4be2b2 100644 --- a/src/application/ApplicationActions.ts +++ b/src/application/ApplicationActions.ts @@ -88,9 +88,9 @@ export const setShareDetailsFromUrl = (type: string, id: string, standalone: boo }); export const SET_STANDALONE_ENABLED = 'APPLICATION/SET_STANDALONE_ENABLED'; -export const setStandaloneEnabled = (standalone: boolean, standaloneProtocol: string, standaloneHost: string, standalonePort: string, standaloneDatabase: string, standaloneDashboardName: string, standaloneDashboardDatabase: string, standaloneDashboardURL: string ) => ({ +export const setStandaloneEnabled = (standalone: boolean, standaloneProtocol: string, standaloneHost: string, standalonePort: string, standaloneDatabase: string, standaloneDashboardName: string, standaloneDashboardDatabase: string, standaloneDashboardURL: string, standaloneUsername: string, standalonePassword: string) => ({ type: SET_STANDALONE_ENABLED, - payload: { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase, standaloneDashboardURL }, + payload: { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase, standaloneDashboardURL, standaloneUsername, standalonePassword }, }); export const SET_STANDALONE_MODE = 'APPLICATION/SET_STANDALONE_MODE'; diff --git a/src/application/ApplicationReducer.ts b/src/application/ApplicationReducer.ts index 48f31c04e..4307b3906 100644 --- a/src/application/ApplicationReducer.ts +++ b/src/application/ApplicationReducer.ts @@ -103,7 +103,7 @@ export const applicationReducer = (state = initialState, action: { type: any; pa } case SET_STANDALONE_ENABLED: { - const { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase, standaloneDashboardURL } = payload; + const { standalone, standaloneProtocol, standaloneHost, standalonePort, standaloneDatabase, standaloneDashboardName, standaloneDashboardDatabase, standaloneDashboardURL, standaloneUsername, standalonePassword } = payload; state = update(state, { standalone: standalone, standaloneProtocol: standaloneProtocol, @@ -112,7 +112,9 @@ export const applicationReducer = (state = initialState, action: { type: any; pa standaloneDatabase: standaloneDatabase, standaloneDashboardName: standaloneDashboardName, standaloneDashboardDatabase: standaloneDashboardDatabase, - standaloneDashboardURL: standaloneDashboardURL + standaloneDashboardURL: standaloneDashboardURL, + standaloneUsername: standaloneUsername, + standalonePassword: standalonePassword }) return state; } diff --git a/src/application/ApplicationSelectors.ts b/src/application/ApplicationSelectors.ts index 94d1d4a28..f577141f7 100644 --- a/src/application/ApplicationSelectors.ts +++ b/src/application/ApplicationSelectors.ts @@ -73,7 +73,9 @@ export const applicationGetStandaloneSettings = (state: any) => { "standaloneDatabase": state.application.standaloneDatabase, "standaloneDashboardName": state.application.standaloneDashboardName, "standaloneDashboardDatabase": state.application.standaloneDashboardDatabase, - "standaloneDashboardURL": state.application.standaloneDashboardURL + "standaloneDashboardURL": state.application.standaloneDashboardURL, + "standaloneUsername": state.application.standaloneUsername, + "standalonePassword": state.application.standalonePassword } } diff --git a/src/application/ApplicationThunks.ts b/src/application/ApplicationThunks.ts index fda23f1ab..2ca4790a1 100644 --- a/src/application/ApplicationThunks.ts +++ b/src/application/ApplicationThunks.ts @@ -272,7 +272,7 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState: dispatch(setSSOEnabled(config['ssoEnabled'], config["ssoDiscoveryUrl"])); const state = getState(); const standalone = config['standalone'];// || (state.application.shareDetails !== undefined && state.application.shareDetails.standalone); - dispatch(setStandaloneEnabled(standalone, config['standaloneProtocol'], config['standaloneHost'], config['standalonePort'], config['standaloneDatabase'], config['standaloneDashboardName'], config['standaloneDashboardDatabase'], config["standaloneDashboardURL"])) + dispatch(setStandaloneEnabled(standalone, config['standaloneProtocol'], config['standaloneHost'], config['standalonePort'], config['standaloneDatabase'], config['standaloneDashboardName'], config['standaloneDashboardDatabase'], config["standaloneDashboardURL"], config['standaloneUsername'], config['standalonePassword'])) dispatch(setConnectionModalOpen(false)); // Auto-upgrade the dashboard version if an old version is cached. diff --git a/src/card/Card.tsx b/src/card/Card.tsx index b8e51d02f..946ba6fd1 100644 --- a/src/card/Card.tsx +++ b/src/card/Card.tsx @@ -57,16 +57,20 @@ const NeoCard = ({ const {driver} = useContext(Neo4jContext); const [databaseList, setDatabaseList] = React.useState([database]) + const [databaseListLoaded, setDatabaseListLoaded] = React.useState(false); // fetching the list of databases from neo4j, filtering out the 'system' db useEffect(() => { - loadDatabaseListFromNeo4j(driver, (result) => { - let index = result.indexOf("system") - if (index > -1) { // only splice array when item is found - result.splice(index, 1); // 2nd parameter means remove one item only - } - setDatabaseList(result) - }); + if(!databaseListLoaded){ + loadDatabaseListFromNeo4j(driver, (result) => { + let index = result.indexOf("system") + if (index > -1) { // only splice array when item is found + result.splice(index, 1); // 2nd parameter means remove one item only + } + setDatabaseList(result) + }); + setDatabaseListLoaded(true); + } }, [report.query]); const [settingsOpen, setSettingsOpen] = React.useState(false); @@ -204,7 +208,7 @@ const NeoCard = ({ const mapStateToProps = (state, ownProps) => ({ report: getReportState(state, ownProps.index), editable: getDashboardIsEditable(state), - database: getDatabase(state, ownProps.dashboardSettings.pagenumber, ownProps.index), + database: getDatabase(state, ownProps && ownProps.dashboardSettings ? ownProps.dashboardSettings.pagenumber : undefined, ownProps.index), globalParameters: getGlobalParameters(state) }); diff --git a/src/dashboard/Dashboard.tsx b/src/dashboard/Dashboard.tsx index ab3efc43d..0967938de 100644 --- a/src/dashboard/Dashboard.tsx +++ b/src/dashboard/Dashboard.tsx @@ -4,7 +4,7 @@ import Container from '@material-ui/core/Container'; import NeoDrawer from './drawer/DashboardDrawer'; import NeoDashboardHeader from './header/DashboardHeader'; import { createDriver, Neo4jProvider, useConnection } from 'use-neo4j'; -import { applicationGetConnection, applicationHasAboutModalOpen } from '../application/ApplicationSelectors'; +import { applicationGetConnection, applicationGetStandaloneSettings, applicationHasAboutModalOpen } from '../application/ApplicationSelectors'; import { connect } from 'react-redux'; import NeoDashboardConnectionUpdateHandler from '../component/misc/DashboardConnectionUpdateHandler'; import { forceRefreshPage } from '../page/PageActions'; @@ -15,7 +15,7 @@ import { downloadComponentAsImage } from '../chart/ChartUtils'; -const Dashboard = ({ pagenumber, connection, onConnectionUpdate, onDownloadDashboardAsImage }) => { +const Dashboard = ({ pagenumber, connection, applicationSettings, onConnectionUpdate, onDownloadDashboardAsImage }) => { const [drawerOpen, setDrawerOpen] = React.useState(false); const driver = createDriver(connection.protocol, connection.url, connection.port, connection.username, connection.password); @@ -43,6 +43,9 @@ const Dashboard = ({ pagenumber, connection, onConnectionUpdate, onDownloadDashb >
+ {applicationSettings.standalonePassword ?
+ Warning: NeoDash is running with a plaintext password in config.json. +
: <>}
@@ -52,7 +55,8 @@ const Dashboard = ({ pagenumber, connection, onConnectionUpdate, onDownloadDashb const mapStateToProps = state => ({ connection: applicationGetConnection(state), - pagenumber: getPageNumber(state) + pagenumber: getPageNumber(state), + applicationSettings: applicationGetStandaloneSettings(state) }); const mapDispatchToProps = dispatch => ({ diff --git a/src/modal/AboutModal.tsx b/src/modal/AboutModal.tsx index 9a649d30f..fd9d6fea7 100644 --- a/src/modal/AboutModal.tsx +++ b/src/modal/AboutModal.tsx @@ -12,7 +12,7 @@ import BugReportIcon from '@material-ui/icons/BugReport'; export const NeoAboutModal = ({ open, handleClose, getDebugState }) => { const app = "NeoDash - Neo4j Dashboard Builder"; - const version = "2.1.8"; + const version = "2.1.9"; const downloadDebugFile = () => { const element = document.createElement("a"); diff --git a/src/report/Report.tsx b/src/report/Report.tsx index 6310d3e69..6f3179cda 100644 --- a/src/report/Report.tsx +++ b/src/report/Report.tsx @@ -56,9 +56,6 @@ export const NeoReport = ({ // Determine the set of fields from the configurations. var numericFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.NUMBER && !REPORT_TYPES[type].selection[field].multiple) : []; - var numericOrDatetimeFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.NUMBER_OR_DATETIME && !REPORT_TYPES[type].selection[field].multiple) : []; - var textFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.TEXT && !REPORT_TYPES[type].selection[field].multiple) : []; - var optionalFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].optional == true) : []; // Take care of multi select fields, they need to be added to the numeric fields too. if (REPORT_TYPES[type].selection) { @@ -69,7 +66,6 @@ export const NeoReport = ({ }); } - const defaultKeyField = (REPORT_TYPES[type].selection) ? Object.keys(REPORT_TYPES[type].selection).find(field => REPORT_TYPES[type].selection[field].key == true) : undefined; const useNodePropsAsFields = REPORT_TYPES[type].useNodePropsAsFields == true; const useReturnValuesAsFields = REPORT_TYPES[type].useReturnValuesAsFields == true; diff --git a/src/settings/SettingsSelectors.ts b/src/settings/SettingsSelectors.ts index fde107482..170c9e8ab 100644 --- a/src/settings/SettingsSelectors.ts +++ b/src/settings/SettingsSelectors.ts @@ -10,9 +10,17 @@ The database related to a card is, at its start, the same as the one defined ins a user can modify the database that is used by a card with a new option inside the card itself. */ export const getDatabase = (state: any, pageNumber:number, cardIndex:number) => { - let reportDatabase = state.dashboard.pages[pageNumber].reports[cardIndex].database - if (reportDatabase != undefined) { + if(state == undefined || pageNumber == undefined || cardIndex == undefined){ + // TODO - use DMBS default database instead of neo4j. + return "neo4j"; + } + if( state.dashboard.pages[pageNumber] == undefined || state.dashboard.pages[pageNumber].reports[cardIndex] == undefined){ + // TODO - use DMBS default database instead of neo4j. + return "neo4j"; + } + let reportDatabase = state.dashboard.pages[pageNumber].reports[cardIndex].database; + if (reportDatabase !== undefined) { return reportDatabase } - return state.application.connection.database.length > 0 ? state.application.connection.database : "neo4j" + return state.application.connection.database ? state.application.connection.database : "neo4j"; } From 4011c9815eb85c4f3853e44cd97bd71ecdd37ac8 Mon Sep 17 00:00:00 2001 From: Niels de Jong Date: Thu, 13 Oct 2022 12:25:04 +0200 Subject: [PATCH 02/11] Fixed bug coming from a race condition in record objects for bar/pie charts (#233) --- src/chart/bar/BarChart.tsx | 42 +++++++++++++++++++++----------------- src/chart/pie/PieChart.tsx | 42 +++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/chart/bar/BarChart.tsx b/src/chart/bar/BarChart.tsx index 90c36419c..24376f998 100644 --- a/src/chart/bar/BarChart.tsx +++ b/src/chart/bar/BarChart.tsx @@ -36,28 +36,32 @@ const NeoBarChart = (props: ChartProps) => { const keys = {}; const data: Record[] = records.reduce((data: Record[], row: Record) => { - if (!selection || !selection['index'] || !selection['value']) { - return data; - } - const index = convertRecordObjectToString(row.get(selection['index'])); - const idx = data.findIndex(item => item.index === index) + try { + if (!selection || !selection['index'] || !selection['value']) { + return data; + } + const index = convertRecordObjectToString(row.get(selection['index'])); + const idx = data.findIndex(item => item.index === index) - const key = selection['key'] !== "(none)" ? recordToNative(row.get(selection['key'])) : selection['value']; - const value = recordToNative(row.get(selection['value'])); + const key = selection['key'] !== "(none)" ? recordToNative(row.get(selection['key'])) : selection['value']; + const value = recordToNative(row.get(selection['value'])); - if (isNaN(value)) { - return data; - } - keys[key] = true; + if (isNaN(value)) { + return data; + } + keys[key] = true; - if (idx > -1) { - data[idx][key] = value - } - else { - data.push({ index, [key]: value }) + if (idx > -1) { + data[idx][key] = value + } + else { + data.push({ index, [key]: value }) + } + return data + } catch (e) { + console.error(e); + return []; } - - return data }, []) .map(row => { Object.keys(keys).forEach(key => { @@ -104,7 +108,7 @@ const NeoBarChart = (props: ChartProps) => { } // TODO: Get rid of duplicate pie slice names... - + return { const keys = {}; const data: Record[] = records.reduce((data: Record[], row: Record) => { - if (!selection || !selection['index'] || !selection['value']) { - return data; - } + try { + if (!selection || !selection['index'] || !selection['value']) { + return data; + } - const index = convertRecordObjectToString(row.get(selection['index'])); - const idx = data.findIndex(item => item.index === index) + const index = convertRecordObjectToString(row.get(selection['index'])); + const idx = data.findIndex(item => item.index === index) + const key = selection['key'] !== "(none)" ? recordToNative(row.get(selection['key'])) : selection['value']; + const value = recordToNative(row.get(selection['value'])); - const key = selection['key'] !== "(none)" ? recordToNative(row.get(selection['key'])) : selection['value']; - const value = recordToNative(row.get(selection['value'])); + if (isNaN(value)) { + return data; + } + keys[key] = true; - if (isNaN(value)) { - return data; - } - keys[key] = true; + if (idx > -1) { + data[idx][key] = value + } + else { + data.push({ id: index, label: index, value: value }) + } - if (idx > -1) { - data[idx][key] = value - } - else { - data.push({ id: index, label: index, value: value }) + return data + } catch (e) { + console.error(e); + return []; } - - return data }, []) .map(row => { Object.keys(keys).forEach(key => { @@ -99,7 +103,7 @@ const NeoPieChart = (props: ChartProps) => { if (data.length == 0) { return } - + return Date: Sat, 15 Oct 2022 08:53:43 +0200 Subject: [PATCH 03/11] Share from self (#232) * Share from self * Workaround desktop * Hide button too --- src/modal/ShareModal.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/modal/ShareModal.tsx b/src/modal/ShareModal.tsx index 04b4cfb53..d4a06a266 100644 --- a/src/modal/ShareModal.tsx +++ b/src/modal/ShareModal.tsx @@ -23,6 +23,7 @@ import { applicationGetConnection } from '../application/ApplicationSelectors'; // const shareBaseURL = "http://localhost:3000"; const shareBaseURL = "http://neodash.graphapp.io"; +const shareLocalURL = window.location.origin.startsWith("file")? shareBaseURL : window.location.origin; const styles = { }; @@ -42,6 +43,8 @@ export const NeoShareModal = ({ connection, loadDashboardListFromNeo4j, loadData const [shareFileURL, setShareFileURL] = React.useState(""); const [shareConnectionDetails, setShareConnectionDetails] = React.useState("No"); const [shareStandalone, setShareStandalone] = React.useState("No"); + const [selfHosted, setSelfHosted] = React.useState("No"); + const [shareLink, setShareLink] = React.useState(null); @@ -164,6 +167,7 @@ export const NeoShareModal = ({ connection, loadDashboardListFromNeo4j, loadData setShareConnectionDetails(e) }} /> + {shareLocalURL != shareBaseURL ? : <>} + { + setShareLink(null); + setSelfHosted(e); + }} />