From ceda481c3c5fc52fa9e1bbeceb5c541a3e66a2ef Mon Sep 17 00:00:00 2001 From: BlackRaven Date: Mon, 28 Aug 2023 20:28:55 -0400 Subject: [PATCH 1/5] first implementation - needs few refinements --- .../pages/developer-guide/configuration.adoc | 34 +++++- .../developer-guide/standalone-mode.adoc | 1 + .../developer-guide/state-management.adoc | 2 + public/config.json | 4 +- scripts/config-entrypoint.sh | 4 +- src/application/ApplicationActions.ts | 12 ++ src/application/ApplicationReducer.ts | 13 +++ src/application/ApplicationSelectors.ts | 15 +++ src/application/ApplicationThunks.ts | 88 ++++++++++++++- src/dashboard/DashboardThunks.ts | 106 +++++++++++++++++- 10 files changed, 270 insertions(+), 9 deletions(-) diff --git a/docs/modules/ROOT/pages/developer-guide/configuration.adoc b/docs/modules/ROOT/pages/developer-guide/configuration.adoc index 6df3fdfba..dfc434822 100644 --- a/docs/modules/ROOT/pages/developer-guide/configuration.adoc +++ b/docs/modules/ROOT/pages/developer-guide/configuration.adoc @@ -24,7 +24,10 @@ will look like this: "standaloneDatabase": "neo4j", "standaloneDashboardName": "My Dashboard", "standaloneDashboardDatabase": "dashboards", - "standaloneDashboardURL": "" + "standaloneDashboardURL": "", + "loggingMode": "0", + "loggingDatabase": "logging" + } .... @@ -84,6 +87,18 @@ use multiple databases. inside Neo4j and would like to run a standalone mode deployment with a dashboard from a URL, set this parameter to the complete URL pointing to the dashboard JSON. + +|loggingMode |string |none |Determines whether neodash should create any +user activity logs. possible values include: `0` (no log is created), +`1` (user login are tracked), `2` (tracks when a specific dashboard is +accessed/opened or saved by a user). +⚠️ Logs are created in Neo4J DB using the current user credentials +(or standaloneUsername if configured); write access to the log database +must be granted to enble any user to create logs. + +|loggingDatabase |string |neo4j |When loggingMode is set to anything +else than '0', the database to use for logging. Log records (nodes) +will be created in this database. |=== == Configuring SSO @@ -134,3 +149,20 @@ The `standaloneDashboardName` and `standaloneDashboardDatabase` config parameters are used to define these. ** A standalone deployment that *reads the fixed dashboard from a URL*. The `standaloneDashboardURL` config parameter is used to define this. + +== Configuring Logging + +NeoDash treats log records with an approach similar to dashboards metadata: +saving them in a Neo4J database as distinct nodes. Each node has a label +"_Neodash_Log" and a standard set of properties such as DateTime (date), +UserID (user), Databadse Name (dashboard) Dashboard Name or UUID (dashboard), +NeoDash mode (neodash_mmode), Action (action) and a short message (message). + +⚠️ Logs are created using the credentials of the current user (either from +logon or, if configured, from standaloneUsername), therefore, for it to work, +administrator must ensure that every neodash user has write access to the +__Neodash_Log_ label in the database confugred in the loggingDatabase prameter (which must also be available on the same Neo4J instance where dashboards and data are stored). +This can be achieved by creating a standard NeoDashUser role in Neo4j with +such permissions and assigning this role to every neoDash user. For more +details on Neo4J granular access control please refer to the link:https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/[product documentation] + diff --git a/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc b/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc index 1259143ed..c9e10f78e 100644 --- a/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc +++ b/docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc @@ -47,6 +47,7 @@ docker run -it --rm -p 5005:5005 \ -e standaloneDatabase="neo4j" \ -e standaloneDashboardName="My Dashboard" \ -e standaloneDashboardDatabase="dashboards" \ + ... neo4jlabs/neodash .... diff --git a/docs/modules/ROOT/pages/developer-guide/state-management.adoc b/docs/modules/ROOT/pages/developer-guide/state-management.adoc index c7513269d..3b2f8ee69 100644 --- a/docs/modules/ROOT/pages/developer-guide/state-management.adoc +++ b/docs/modules/ROOT/pages/developer-guide/state-management.adoc @@ -134,6 +134,8 @@ standalone mode. "standaloneDatabase": "neo4j", "standaloneDashboardName": "My Dashboard", "standaloneDashboardDatabase": "dashboards", + "loggingMode": "0", + "loggingDatabase": "logging", "notificationIsDismissable": null } .... diff --git a/public/config.json b/public/config.json index 6a203041f..615fd0d7c 100644 --- a/public/config.json +++ b/public/config.json @@ -8,5 +8,7 @@ "standaloneDatabase": "neo4j", "standaloneDashboardName": "My Dashboard", "standaloneDashboardDatabase": "dashboards", - "standaloneDashboardURL": "" + "standaloneDashboardURL": "", + "loggingMode": "2", + "loggingDatabase": "logs" } diff --git a/scripts/config-entrypoint.sh b/scripts/config-entrypoint.sh index 25374b647..1c5c60297 100644 --- a/scripts/config-entrypoint.sh +++ b/scripts/config-entrypoint.sh @@ -15,5 +15,7 @@ echo " \ \"standalonePassword\": \"${standalonePassword:=}\", \ \"standaloneDashboardName\": \"${standaloneDashboardName:='My Dashboard'}\", \ \"standaloneDashboardDatabase\": \"${standaloneDashboardDatabase:='neo4j'}\", \ - \"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\" \ + \"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\", \ + \"loggingMode\": \"${loggingMode:='0'}\", \ + \"loggingMode\": \"${loggingDatabase:='logs'}\" \ }" > /usr/share/nginx/html/config.json diff --git a/src/application/ApplicationActions.ts b/src/application/ApplicationActions.ts index 49311d768..f0021c433 100644 --- a/src/application/ApplicationActions.ts +++ b/src/application/ApplicationActions.ts @@ -172,6 +172,18 @@ export const setStandaloneDashboardDatabase = (dashboardDatabase: string) => ({ payload: { dashboardDatabase }, }); +export const SET_LOGGING_MODE = 'APPLICATION/SET_LOGGING_MODE'; +export const setLoggingMode = (loggingMode: string) => ({ + type: SET_LOGGING_MODE, + payload: { loggingMode }, +}); + +export const SET_LOGGING_DATABASE = 'APPLICATION/SET_LOGGING_DATABASE'; +export const setLoggingDatabase = (loggingDatabase: string) => ({ + type: SET_LOGGING_DATABASE, + payload: { loggingDatabase }, +}); + export const SET_SSO_ENABLED = 'APPLICATION/SET_SSO_ENABLED'; export const setSSOEnabled = (enabled: boolean, discoveryUrl: string) => ({ type: SET_SSO_ENABLED, diff --git a/src/application/ApplicationReducer.ts b/src/application/ApplicationReducer.ts index 23dcdd9a1..047e0e0c3 100644 --- a/src/application/ApplicationReducer.ts +++ b/src/application/ApplicationReducer.ts @@ -24,6 +24,8 @@ import { SET_STANDALONE_DASHBOARD_DATEBASE, SET_STANDALONE_ENABLED, SET_STANDALONE_MODE, + SET_LOGGING_MODE, + SET_LOGGING_DATABASE, SET_WAIT_FOR_SSO, SET_WELCOME_SCREEN_OPEN, } from './ApplicationActions'; @@ -50,6 +52,7 @@ const initialState = { dashboardToLoadAfterConnecting: null, waitForSSO: false, standalone: false, + loggingMode: '0', }; export const applicationReducer = (state = initialState, action: { type: any; payload: any }) => { const { type, payload } = action; @@ -107,6 +110,16 @@ export const applicationReducer = (state = initialState, action: { type: any; pa state = update(state, { standalone: standalone }); return state; } + case SET_LOGGING_MODE: { + const { loggingMode } = payload; + state = update(state, { loggingMode: loggingMode }); + return state; + } + case SET_LOGGING_DATABASE: { + const { loggingDatabase } = payload; + state = update(state, { loggingDatabase: loggingDatabase }); + return state; + } case SET_SSO_ENABLED: { const { enabled, discoveryUrl } = payload; state = update(state, { ssoEnabled: enabled, ssoDiscoveryUrl: discoveryUrl }); diff --git a/src/application/ApplicationSelectors.ts b/src/application/ApplicationSelectors.ts index 263fae4bf..66cfaf69f 100644 --- a/src/application/ApplicationSelectors.ts +++ b/src/application/ApplicationSelectors.ts @@ -33,6 +33,10 @@ export const applicationGetConnectionDatabase = (state: any) => { return state.application.connection.database; }; +export const applicationGetConnectionUser = (state: any) => { + return state.application.connection.username; +} + export const applicationGetShareDetails = (state: any) => { return state.application.shareDetails; }; @@ -41,6 +45,10 @@ export const applicationIsStandalone = (state: any) => { return state.application.standalone; }; +export const applicationGetLoggingMode = (state: any) => { + return state.application.loggingMode; +}; + export const applicationHasNeo4jDesktopConnection = (state: any) => { return state.application.desktopConnection != null; }; @@ -84,6 +92,13 @@ export const applicationGetStandaloneSettings = (state: any) => { }; }; +export const applicationGetLoggingSettings = (state: any) => { + return { + loggingMode: state.application.loggingMode, + loggingDatabase: state.application.loggingDatabase, + }; +}; + export const applicationHasWelcomeScreenOpen = (state: any) => { return state.application.welcomeScreenOpen; }; diff --git a/src/application/ApplicationThunks.ts b/src/application/ApplicationThunks.ts index d09ee1ed5..bef32dda3 100644 --- a/src/application/ApplicationThunks.ts +++ b/src/application/ApplicationThunks.ts @@ -34,11 +34,15 @@ import { setAboutModalOpen, setStandaloneMode, setStandaloneDashboardDatabase, + setLoggingMode, + setLoggingDatabase, setWaitForSSO, setParametersToLoadAfterConnecting, setReportHelpModalOpen, } from './ApplicationActions'; import { version } from '../modal/AboutModal'; +import { applicationGetLoggingSettings, applicationIsStandalone } from './ApplicationSelectors'; +import { createUUID } from '../utils/uuid'; /** * Application Thunks (https://redux.js.org/usage/writing-logic-thunks) handle complex state manipulations. @@ -56,6 +60,9 @@ import { version } from '../modal/AboutModal'; */ export const createConnectionThunk = (protocol, url, port, database, username, password) => (dispatch: any, getState: any) => { + const loggingState = getState(); + const loggingSettings = applicationGetLoggingSettings(loggingState) + const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' try { const driver = createDriver(protocol, url, port, username, password, { userAgent: `neodash/v${version}` }); // eslint-disable-next-line no-console @@ -65,13 +72,41 @@ export const createConnectionThunk = console.log('Confirming connection was established...'); if (records && records[0] && records[0].error) { dispatch(createNotificationThunk('Unable to establish connection', records[0].error)); - } else if (records && records[0] && records[0].keys[0] == 'connected') { + if (loggingSettings.loggingMode>'0'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + username, + 'ERR - connect to DB', + database, + '', + 'Error while trying to establish connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now() + ) + ); + } + } else if (records && records[0] && records[0].keys[0] == 'connected') { dispatch(setConnectionProperties(protocol, url, port, database, username, password)); dispatch(setConnectionModalOpen(false)); dispatch(setConnected(true)); dispatch(updateSessionParameterThunk('session_uri', `${protocol}://${url}:${port}`)); dispatch(updateSessionParameterThunk('session_database', database)); dispatch(updateSessionParameterThunk('session_username', username)); + if (loggingSettings.loggingMode>'0'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + username, + 'INF - connect to DB', + database, + '', + username +'established connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now() + ) + ); + } // If we have remembered to load a specific dashboard after connecting to the database, take care of it here. const { application } = getState(); if ( @@ -349,6 +384,8 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState: standaloneDashboardName: 'My Dashboard', standaloneDashboardDatabase: 'dashboards', standaloneDashboardURL: '', + loggingMode: '0', + loggingDatabase: 'logs', }; try { config = await (await fetch('config.json')).json(); @@ -393,6 +430,10 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState: config.standalonePassword ) ); + + dispatch(setLoggingMode(config.loggingMode)); + dispatch(setLoggingDatabase(config.loggingDatabase)); + dispatch(setConnectionModalOpen(false)); // Auto-upgrade the dashboard version if an old version is cached. @@ -595,3 +636,48 @@ export const initializeApplicationAsStandaloneThunk = dispatch(setConnectionModalOpen(true)); } }; + + // Thunk to handle log events. +export const createLogThunk = +(loggingDriver, loggingDatabase, neodashMode, logUser, logAction, logDatabase, logDashboard = '', logMessage) => +(dispatch: any) => { + try { + alert('entered log ceation with following parameters:\n'+loggingDriver+'\n'+neodashMode+'\n'+logUser+'\n'+logAction+'\n'+logDatabase+'\n'+logDashboard+'\n'+logMessage) + const uuid = createUUID(); + + // Generate a cypher query to save the log. + const query = 'CREATE (n:_Neodash_Log) SET n.uuid = $uuid, n.user = $user, n.date = datetime(), n.neodash_mode = $neodashMode, n.action = $logAction, n.database = $logDatabase, n.dashboard = $logDashboard, n.message = $logMessage RETURN $uuid as uuid' + + const parameters = { + uuid: uuid, + user: logUser, + logAction: logAction, + logDatabase: logDatabase, + neodashMode: neodashMode, + logDashboard: logDashboard, + logMessage: logMessage + }; + runCypherQuery( + loggingDriver, + loggingDatabase, + query, + parameters, + 1, + () => {}, + (records) => { + if (records && records[0] && records[0]._fields && records[0]._fields[0] && records[0]._fields[0] == uuid) { + dispatch(createNotificationThunk('🎉 Success!', 'log created: '+uuid)); + } else { + dispatch( + createNotificationThunk( + 'Error creating log', + `Please check logging configuration with your Neodash administrator` + ) + ); + } + } + ); + } catch (e) { + dispatch(createNotificationThunk('Error creating log', e)); + } +}; \ No newline at end of file diff --git a/src/dashboard/DashboardThunks.ts b/src/dashboard/DashboardThunks.ts index da3526805..30f429d6f 100644 --- a/src/dashboard/DashboardThunks.ts +++ b/src/dashboard/DashboardThunks.ts @@ -5,6 +5,8 @@ import { runCypherQuery } from '../report/ReportQueryRunner'; import { setParametersToLoadAfterConnecting, setWelcomeScreenOpen } from '../application/ApplicationActions'; import { updateGlobalParametersThunk, updateParametersToNeo4jTypeThunk } from '../settings/SettingsThunks'; import { createUUID } from '../utils/uuid'; +import { createLogThunk } from '../application/ApplicationThunks'; +import { applicationGetLoggingSettings, applicationGetConnectionUser, applicationIsStandalone } from '../application/ApplicationSelectors'; export const removePageThunk = (number) => (dispatch: any, getState: any) => { try { @@ -198,8 +200,13 @@ export const saveDashboardToNeo4jThunk = } }; -export const loadDashboardFromNeo4jByUUIDThunk = (driver, database, uuid, callback) => (dispatch: any) => { - try { +export const loadDashboardFromNeo4jByUUIDThunk = (driver, database, uuid, callback) => (dispatch: any, getState: any) => { +const loggingState = getState(); +const loggingSettings = applicationGetLoggingSettings(loggingState) +const loguser = applicationGetConnectionUser(loggingState) +const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' +alert('loggingsettings: '+loggingSettings.loggingMode+'\n'+loggingSettings.loggingDatabase+'\n'+loguser+'\n'+neodashMode) +try { const query = 'MATCH (n:_Neodash_Dashboard) WHERE n.uuid = $uuid RETURN n.content as dashboard'; runCypherQuery( driver, @@ -216,17 +223,64 @@ export const loadDashboardFromNeo4jByUUIDThunk = (driver, database, uuid, callba `A dashboard with UUID '${uuid}' could not be found.` ) ); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - load dashboard', + database, + 'UUID:'+uuid, + 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date.now() + ) + ); + } } - callback(records[0]._fields[0]); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'INF - load dashboard', + database, + 'UUID:'+uuid, + 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date.now() + ) + ); + } + callback(records[0]._fields[0]); } ); } catch (e) { dispatch(createNotificationThunk('Unable to load dashboard to Neo4j', e)); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - load dashboard', + database, + 'UUID:'+uuid, + 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date.now() + ) + ); + } } }; -export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callback) => (dispatch: any) => { - try { +export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callback) => (dispatch: any, getState: any) => { + const loggingState = getState(); + const loggingSettings = applicationGetLoggingSettings(loggingState) + const loguser = applicationGetConnectionUser(loggingState) + const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' + alert('loggingsettings: '+loggingSettings.loggingMode+'\n'+loggingSettings.loggingDatabase+'\n'+loguser+'\n'+neodashMode) + try { const query = 'MATCH (d:_Neodash_Dashboard) WHERE d.title = $name RETURN d.content as dashboard ORDER by d.date DESC LIMIT 1'; runCypherQuery( @@ -244,14 +298,56 @@ export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callba 'A dashboard with the provided name could not be found.' ) ); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - load dashboard', + database, + 'Name:'+name, + 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date.now() + ) + ); + } return; } if (records[0].error) { dispatch(createNotificationThunk('Unable to load dashboard.', records[0].error)); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - load dashboard', + database, + 'Name:'+name, + 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date.now() + ) + ); + } return; } + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'INF - load dashboard', + database, + 'Name:'+name, + 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date.now() + ) + ); + } callback(records[0]._fields[0]); } ); From bdfeaca69e935ff9deefa1b7ccfba230069f0bde Mon Sep 17 00:00:00 2001 From: BlackRaven Date: Wed, 30 Aug 2023 13:22:02 -0400 Subject: [PATCH 2/5] index on feature/log: ceda481c first implementation - needs few refinements From a826d1ab97a3bd5ef1a804f963565d9d06639d54 Mon Sep 17 00:00:00 2001 From: BlackRaven Date: Wed, 30 Aug 2023 18:52:16 -0400 Subject: [PATCH 3/5] polished --- .../pages/developer-guide/configuration.adoc | 6 +- public/config.json | 4 +- src/dashboard/Dashboard.tsx | 1 - src/dashboard/DashboardThunks.ts | 111 +++++++++--------- src/modal/ConnectionModal.tsx | 4 +- 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/docs/modules/ROOT/pages/developer-guide/configuration.adoc b/docs/modules/ROOT/pages/developer-guide/configuration.adoc index dfc434822..12fec8955 100644 --- a/docs/modules/ROOT/pages/developer-guide/configuration.adoc +++ b/docs/modules/ROOT/pages/developer-guide/configuration.adoc @@ -91,11 +91,14 @@ the dashboard JSON. |loggingMode |string |none |Determines whether neodash should create any user activity logs. possible values include: `0` (no log is created), `1` (user login are tracked), `2` (tracks when a specific dashboard is -accessed/opened or saved by a user). +accessed/loaded or saved by a user*). + ⚠️ Logs are created in Neo4J DB using the current user credentials (or standaloneUsername if configured); write access to the log database must be granted to enble any user to create logs. +⚠️ * Load/Save from/to file are not logged (only from/to Database) + |loggingDatabase |string |neo4j |When loggingMode is set to anything else than '0', the database to use for logging. Log records (nodes) will be created in this database. @@ -165,4 +168,3 @@ __Neodash_Log_ label in the database confugred in the loggingDatabase prameter ( This can be achieved by creating a standard NeoDashUser role in Neo4j with such permissions and assigning this role to every neoDash user. For more details on Neo4J granular access control please refer to the link:https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/[product documentation] - diff --git a/public/config.json b/public/config.json index 31c9e27e7..11e681a1c 100644 --- a/public/config.json +++ b/public/config.json @@ -3,9 +3,9 @@ "ssoDiscoveryUrl": "https://example.com", "standalone": false, "standaloneProtocol": "neo4j", - "standaloneHost": "192.168.0.100", + "standaloneHost": "localhost", "standalonePort": "7687", - "standaloneDatabase": "movies", + "standaloneDatabase": "neo4j", "standaloneDashboardName": "My Dashboard", "standaloneDashboardDatabase": "dashboards", "standaloneDashboardURL": "", diff --git a/src/dashboard/Dashboard.tsx b/src/dashboard/Dashboard.tsx index d06c16eda..04fc8fa39 100644 --- a/src/dashboard/Dashboard.tsx +++ b/src/dashboard/Dashboard.tsx @@ -75,7 +75,6 @@ const Dashboard = ({ ); - alert ('I am here and I am a dashboard') return content; }; diff --git a/src/dashboard/DashboardThunks.ts b/src/dashboard/DashboardThunks.ts index eca09c110..a7e8f6990 100644 --- a/src/dashboard/DashboardThunks.ts +++ b/src/dashboard/DashboardThunks.ts @@ -7,8 +7,6 @@ import { updateGlobalParametersThunk, updateParametersToNeo4jTypeThunk } from '. import { createUUID } from '../utils/uuid'; import { createLogThunk } from '../application/ApplicationThunks'; import { applicationGetLoggingSettings, applicationGetConnectionUser, applicationIsStandalone } from '../application/ApplicationSelectors'; -// import { Neo4jContext, Neo4jContextState } from 'use-neo4j/dist/neo4j.context'; -// import { useContext } from 'react'; export const removePageThunk = (number) => (dispatch: any, getState: any) => { try { @@ -56,12 +54,7 @@ export const movePageThunk = (oldIndex: number, newIndex: number) => (dispatch: }; export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { - const loggingState = getState(); - const loggingSettings = applicationGetLoggingSettings(loggingState) - const loguser = applicationGetConnectionUser(loggingState) - const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' - - try { + try { if (text.length == 0) { throw 'No dashboard file specified. Did you select a file?'; } @@ -75,7 +68,6 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { if (dashboard._persist && dashboard.application && dashboard.dashboard) { dispatch( createNotificationThunk('Loaded a Debug Report', "Recovery-mode active. All report types were set to 'table'.") - ); dashboard.dashboard.pages.map((p) => { p.reports.map((r) => { @@ -142,9 +134,9 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { // Reverse engineer the minimal set of fields from the selection loaded. dashboard.pages.forEach((p) => { - r.fields = []; - p.reports.forEach((r) => { + p.reports.forEach((r) => { if (r.selection) { + r.fields = []; Object.keys(r.selection).forEach((f) => { r.fields.push([f, r.selection[f]]); }); @@ -153,21 +145,6 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { }); dispatch(setDashboard(dashboard)); - // if(loggingSettings.loggingMode > '1') { - // const { driver } = useContext(Neo4jContext); - // dispatch( - // createLogThunk( - // driver, - // loggingSettings.loggingDatabase, - // neodashMode, - // loguser, - // 'INF - load dashboard from file', - // loggingSettings.loggingDatabase, - // 'Name:'+dashboard.title, - // 'User '+loguser+' Loaded dashboard from file in '+neodashMode+' mode at '+Date.now() - // ) - // ) - // } const { application } = getState(); @@ -175,21 +152,6 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { dispatch(setParametersToLoadAfterConnecting(null)); dispatch(updateParametersToNeo4jTypeThunk()); } catch (e) { - // if(loggingSettings.loggingMode > '1') { - // const { driver } = useContext(Neo4jContext); - // dispatch( - // createLogThunk( - // driver, - // loggingSettings.loggingDatabase, - // neodashMode, - // loguser, - // 'ERR - load dashboard from file', - // loggingSettings.loggingDatabase, - // 'Name:Not Parsed', - // 'Error while trying to load dashboard from file in '+neodashMode+' mode at '+Date.now() - // ) - // ) - // } dispatch(createNotificationThunk('Unable to load dashboard', e)); } }; @@ -197,12 +159,12 @@ export const loadDashboardThunk = (text) => (dispatch: any, getState: any) => { export const saveDashboardToNeo4jThunk = (driver, database, dashboard, date, user, overwrite = false) => (dispatch: any, getState: any) => { - const loggingState = getState(); - const loggingSettings = applicationGetLoggingSettings(loggingState) - const loguser = applicationGetConnectionUser(loggingState) - const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' + const loggingState = getState(); + const loggingSettings = applicationGetLoggingSettings(loggingState) + const loguser = applicationGetConnectionUser(loggingState) + const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' - try { + try { const uuid = createUUID(); const { title, version } = dashboard; @@ -229,6 +191,20 @@ export const saveDashboardToNeo4jThunk = (records) => { if (records && records[0] && records[0]._fields && records[0]._fields[0] && records[0]._fields[0] == uuid) { dispatch(createNotificationThunk('🎉 Success!', 'Your current dashboard was saved to Neo4j.')); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'INF - save dashboard', + database, + 'Name:'+title, + 'User '+loguser+' saved dashboard to Neo4J in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) + ) + ); + } } else { dispatch( createNotificationThunk( @@ -236,11 +212,41 @@ export const saveDashboardToNeo4jThunk = `Do you have write access to the '${database}' database?` ) ); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - save dashboard', + database, + 'Name:'+title, + 'Error while trying to save dashboard to Neo4J in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) + ) + ); + } + } } ); } catch (e) { dispatch(createNotificationThunk('Unable to save dashboard to Neo4j', e)); + if (loggingSettings.loggingMode > '1'){ + dispatch( + createLogThunk( + driver, + loggingSettings.loggingDatabase, + neodashMode, + loguser, + 'ERR - save dashboard', + database, + 'Name:Not fetched', + 'Error while trying to save dashboard to Neo4J in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) + ) + ); + } + } }; @@ -276,7 +282,7 @@ try { 'ERR - load dashboard', database, 'UUID:'+uuid, - 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date.now() + 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -291,7 +297,7 @@ try { 'INF - load dashboard', database, 'UUID:'+uuid, - 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date.now() + 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -310,7 +316,7 @@ try { 'ERR - load dashboard', database, 'UUID:'+uuid, - 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date.now() + 'Error while trying to load dashboard by UUID in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -322,7 +328,6 @@ export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callba const loggingSettings = applicationGetLoggingSettings(loggingState) const loguser = applicationGetConnectionUser(loggingState) const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor' - alert('loggingsettings: '+loggingSettings.loggingMode+'\n'+loggingSettings.loggingDatabase+'\n'+loguser+'\n'+neodashMode) try { const query = 'MATCH (d:_Neodash_Dashboard) WHERE d.title = $name RETURN d.content as dashboard ORDER by d.date DESC LIMIT 1'; @@ -351,7 +356,7 @@ export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callba 'ERR - load dashboard', database, 'Name:'+name, - 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date.now() + 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -370,7 +375,7 @@ export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callba 'ERR - load dashboard', database, 'Name:'+name, - 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date.now() + 'Error while trying to load dashboard by Name in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -387,7 +392,7 @@ export const loadDashboardFromNeo4jByNameThunk = (driver, database, name, callba 'INF - load dashboard', database, 'Name:'+name, - 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date.now() + 'User '+loguser+' Loaded dashboard by UUID in '+neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } diff --git a/src/modal/ConnectionModal.tsx b/src/modal/ConnectionModal.tsx index ad98a65b3..bb9d931a8 100644 --- a/src/modal/ConnectionModal.tsx +++ b/src/modal/ConnectionModal.tsx @@ -217,8 +217,8 @@ export default function NeoConnectionModal({
{standaloneSettings.standaloneDashboardURL === '' ? ( <> - Sign in to continue. You will be connected to Neo4j, and load a dashboard called - {standaloneSettings.standaloneDashboardName}. + Sign in to continue. You will be connected to Neo4j, and load a dashboard called + {standaloneSettings.standaloneDashboardName}. ) : ( <> Sign in to continue. You will be connected to Neo4j, and load a dashboard. From 15fac3e5667b250dac8f3a7dd2d2415e85d10406 Mon Sep 17 00:00:00 2001 From: BlackRaven Date: Thu, 31 Aug 2023 01:06:23 -0400 Subject: [PATCH 4/5] fix on error notification --- src/application/ApplicationActions.ts | 4 +-- src/application/ApplicationReducer.ts | 2 +- src/application/ApplicationSelectors.ts | 5 --- src/application/ApplicationThunks.ts | 45 ++++++++++++++++--------- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/application/ApplicationActions.ts b/src/application/ApplicationActions.ts index d2e5d838e..2993431ba 100644 --- a/src/application/ApplicationActions.ts +++ b/src/application/ApplicationActions.ts @@ -185,9 +185,9 @@ export const setLoggingDatabase = (loggingDatabase: string) => ({ }); export const SET_LOG_ERROR_NOTIFICATION = 'APPLICATION/SET_LOG_ERROR_NOTIFICATION'; -export const setLogErrorNotification = (parameters: any) => ({ +export const setLogErrorNotification = (logErrorNotification: any) => ({ type: SET_LOG_ERROR_NOTIFICATION, - payload: { parameters }, + payload: { logErrorNotification }, }); export const SET_SSO_ENABLED = 'APPLICATION/SET_SSO_ENABLED'; diff --git a/src/application/ApplicationReducer.ts b/src/application/ApplicationReducer.ts index 4a6f56f46..13dbf8e6f 100644 --- a/src/application/ApplicationReducer.ts +++ b/src/application/ApplicationReducer.ts @@ -54,7 +54,7 @@ const initialState = { waitForSSO: false, standalone: false, loggingMode: '0', - logErrorNotification: 3, + logErrorNotification: '3', }; export const applicationReducer = (state = initialState, action: { type: any; payload: any }) => { const { type, payload } = action; diff --git a/src/application/ApplicationSelectors.ts b/src/application/ApplicationSelectors.ts index 7a57b3bb5..4200bed73 100644 --- a/src/application/ApplicationSelectors.ts +++ b/src/application/ApplicationSelectors.ts @@ -96,11 +96,6 @@ export const applicationGetLoggingSettings = (state: any) => { return { loggingMode: state.application.loggingMode, loggingDatabase: state.application.loggingDatabase, - }; -}; - -export const applicationGetLogErrorNotification = (state: any) => { - return { logErrorNotification: state.application.logErrorNotification, }; }; diff --git a/src/application/ApplicationThunks.ts b/src/application/ApplicationThunks.ts index 0c201a373..61e07c352 100644 --- a/src/application/ApplicationThunks.ts +++ b/src/application/ApplicationThunks.ts @@ -42,8 +42,9 @@ import { setReportHelpModalOpen, } from './ApplicationActions'; import { version } from '../modal/AboutModal'; -import { applicationGetLoggingSettings, applicationIsStandalone, applicationGetLogErrorNotification } from './ApplicationSelectors'; +import { applicationGetLoggingSettings, applicationIsStandalone } from './ApplicationSelectors'; import { createUUID } from '../utils/uuid'; +import { valueIsRelationship } from '../chart/ChartUtils'; /** * Application Thunks (https://redux.js.org/usage/writing-logic-thunks) handle complex state manipulations. @@ -83,7 +84,7 @@ export const createConnectionThunk = 'ERR - connect to DB', database, '', - 'Error while trying to establish connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now() + 'Error while trying to establish connection to Neo4j DB in ' +neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -104,7 +105,7 @@ export const createConnectionThunk = 'INF - connect to DB', database, '', - username +'established connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now() + username +'established connection to Neo4j DB in ' +neodashMode+' mode at '+Date(Date.now()).substring(0,33) ) ); } @@ -387,6 +388,7 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState: standaloneDashboardURL: '', loggingMode: '0', loggingDatabase: 'logs', + logErrorNotification: '3', }; try { config = await (await fetch('config.json')).json(); @@ -434,6 +436,7 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState: dispatch(setLoggingMode(config.loggingMode)); dispatch(setLoggingDatabase(config.loggingDatabase)); + dispatch(setLogErrorNotification('3')); dispatch(setConnectionModalOpen(false)); @@ -644,7 +647,6 @@ export const createLogThunk = (dispatch: any, getState: any) => { try { const uuid = createUUID(); - // Generate a cypher query to save the log. const query = 'CREATE (n:_Neodash_Log) SET n.uuid = $uuid, n.user = $user, n.date = datetime(), n.neodash_mode = $neodashMode, n.action = $logAction, n.database = $logDatabase, n.dashboard = $logDashboard, n.message = $logMessage RETURN $uuid as uuid' @@ -670,26 +672,37 @@ export const createLogThunk = } else { //we only show error notification one time const state = getState() - const LogErrorNotification : number = Number(applicationGetLogErrorNotification(state)) - if (LogErrorNotification > 0 ) { - dispatch( + const loggingSettings = applicationGetLoggingSettings (state); + var LogErrorNotificationNum = Number(loggingSettings.logErrorNotification) + console.log('Error creating log for '+((LogErrorNotificationNum-4)*(-1))+' times') + if (LogErrorNotificationNum > 0 ) { + dispatch( createNotificationThunk( 'Error creating log', - `Please check logging configuration with your Neodash administrator` - ) + LogErrorNotificationNum>1 ? `Please check logging configuration with your Neodash administrator`:`Please check logging configuration with your Neodash administrator - This message will not be displayed anymore in the current session` + ) ); - dispatch(setLogErrorNotification (LogErrorNotification - 1)); } - } + LogErrorNotificationNum = LogErrorNotificationNum-1 + dispatch(setLogErrorNotification (LogErrorNotificationNum.toString())); + } } ); } catch (e) { //we only show error notification 3 times const state = getState() - const LogErrorNotification : number = Number(applicationGetLogErrorNotification(state)) - if (LogErrorNotification > 0 ) { - dispatch(createNotificationThunk('Error creating log', e)); - dispatch(setLogErrorNotification (LogErrorNotification - 1)); + const loggingSettings = applicationGetLoggingSettings (state); + var LogErrorNotificationNum = Number(loggingSettings.logErrorNotification) + console.log('Error creating log for '+((LogErrorNotificationNum-4)*(-1))+' times') + if (LogErrorNotificationNum > 0 ) { + dispatch( + createNotificationThunk( + 'Error creating log', + LogErrorNotificationNum>1 ? `Please check logging configuration with your Neodash administrator`:`Please check logging configuration with your Neodash administrator - This message will not be displayed anymore in the current session` + ) + ); } - } + LogErrorNotificationNum = LogErrorNotificationNum-1 + dispatch(setLogErrorNotification (LogErrorNotificationNum.toString())); + } }; \ No newline at end of file From 7b88f4eb2d1b1c232620fbf1b2c74ef0fbda7c4f Mon Sep 17 00:00:00 2001 From: BlackRaven Date: Thu, 31 Aug 2023 08:33:13 -0400 Subject: [PATCH 5/5] minor refinement in documentation --- .../pages/developer-guide/configuration.adoc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/developer-guide/configuration.adoc b/docs/modules/ROOT/pages/developer-guide/configuration.adoc index 12fec8955..65f61982e 100644 --- a/docs/modules/ROOT/pages/developer-guide/configuration.adoc +++ b/docs/modules/ROOT/pages/developer-guide/configuration.adoc @@ -164,7 +164,19 @@ NeoDash mode (neodash_mmode), Action (action) and a short message (message). ⚠️ Logs are created using the credentials of the current user (either from logon or, if configured, from standaloneUsername), therefore, for it to work, administrator must ensure that every neodash user has write access to the -__Neodash_Log_ label in the database confugred in the loggingDatabase prameter (which must also be available on the same Neo4J instance where dashboards and data are stored). +__Neodash_Log_ label in the database confugred in the loggingDatabase prameter +(which must also be available on the same Neo4J instance where dashboards and +data are stored). This can be achieved by creating a standard NeoDashUser role in Neo4j with such permissions and assigning this role to every neoDash user. For more -details on Neo4J granular access control please refer to the link:https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/[product documentation] +details on Neo4J granular access control please refer to the link: +https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/[product documentation] +In case of any misconfiguration (e.g. user not having grants to write in the +loggingDatabase), the application will show an error notification every time +a log creation fails (e.g. for a loggingMode set to '2', at user connection +attempt and any subsequent attempt to load or save a dashboard); the error will +not block the application flow (users will still be able to connect, load and +save dashboards normally); additionally, to limit user discomfort in such +scenario, the notification is shown for 3 times only in a single user session. +After 3rd time, a line will still be logged in the javascript console of user +browser but interactive notifictions will be suppressed. \ No newline at end of file