Skip to content

Commit ceda481

Browse files
committed
first implementation - needs few refinements
1 parent 6b67009 commit ceda481

File tree

10 files changed

+270
-9
lines changed

10 files changed

+270
-9
lines changed

docs/modules/ROOT/pages/developer-guide/configuration.adoc

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ will look like this:
2424
"standaloneDatabase": "neo4j",
2525
"standaloneDashboardName": "My Dashboard",
2626
"standaloneDashboardDatabase": "dashboards",
27-
"standaloneDashboardURL": ""
27+
"standaloneDashboardURL": "",
28+
"loggingMode": "0",
29+
"loggingDatabase": "logging"
30+
2831
}
2932
....
3033

@@ -84,6 +87,18 @@ use multiple databases.
8487
inside Neo4j and would like to run a standalone mode deployment with a
8588
dashboard from a URL, set this parameter to the complete URL pointing to
8689
the dashboard JSON.
90+
91+
|loggingMode |string |none |Determines whether neodash should create any
92+
user activity logs. possible values include: `0` (no log is created),
93+
`1` (user login are tracked), `2` (tracks when a specific dashboard is
94+
accessed/opened or saved by a user).
95+
⚠️ Logs are created in Neo4J DB using the current user credentials
96+
(or standaloneUsername if configured); write access to the log database
97+
must be granted to enble any user to create logs.
98+
99+
|loggingDatabase |string |neo4j |When loggingMode is set to anything
100+
else than '0', the database to use for logging. Log records (nodes)
101+
will be created in this database.
87102
|===
88103

89104
== Configuring SSO
@@ -134,3 +149,20 @@ The `standaloneDashboardName` and `standaloneDashboardDatabase` config
134149
parameters are used to define these.
135150
** A standalone deployment that *reads the fixed dashboard from a URL*.
136151
The `standaloneDashboardURL` config parameter is used to define this.
152+
153+
== Configuring Logging
154+
155+
NeoDash treats log records with an approach similar to dashboards metadata:
156+
saving them in a Neo4J database as distinct nodes. Each node has a label
157+
"_Neodash_Log" and a standard set of properties such as DateTime (date),
158+
UserID (user), Databadse Name (dashboard) Dashboard Name or UUID (dashboard),
159+
NeoDash mode (neodash_mmode), Action (action) and a short message (message).
160+
161+
⚠️ Logs are created using the credentials of the current user (either from
162+
logon or, if configured, from standaloneUsername), therefore, for it to work,
163+
administrator must ensure that every neodash user has write access to the
164+
__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).
165+
This can be achieved by creating a standard NeoDashUser role in Neo4j with
166+
such permissions and assigning this role to every neoDash user. For more
167+
details on Neo4J granular access control please refer to the link:https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/[product documentation]
168+

docs/modules/ROOT/pages/developer-guide/standalone-mode.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ docker run -it --rm -p 5005:5005 \
4747
-e standaloneDatabase="neo4j" \
4848
-e standaloneDashboardName="My Dashboard" \
4949
-e standaloneDashboardDatabase="dashboards" \
50+
...
5051
neo4jlabs/neodash
5152
....
5253

docs/modules/ROOT/pages/developer-guide/state-management.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ standalone mode.
134134
"standaloneDatabase": "neo4j",
135135
"standaloneDashboardName": "My Dashboard",
136136
"standaloneDashboardDatabase": "dashboards",
137+
"loggingMode": "0",
138+
"loggingDatabase": "logging",
137139
"notificationIsDismissable": null
138140
}
139141
....

public/config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
"standaloneDatabase": "neo4j",
99
"standaloneDashboardName": "My Dashboard",
1010
"standaloneDashboardDatabase": "dashboards",
11-
"standaloneDashboardURL": ""
11+
"standaloneDashboardURL": "",
12+
"loggingMode": "2",
13+
"loggingDatabase": "logs"
1214
}

scripts/config-entrypoint.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ echo " \
1515
\"standalonePassword\": \"${standalonePassword:=}\", \
1616
\"standaloneDashboardName\": \"${standaloneDashboardName:='My Dashboard'}\", \
1717
\"standaloneDashboardDatabase\": \"${standaloneDashboardDatabase:='neo4j'}\", \
18-
\"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\" \
18+
\"standaloneDashboardURL\": \"${standaloneDashboardURL:=}\", \
19+
\"loggingMode\": \"${loggingMode:='0'}\", \
20+
\"loggingMode\": \"${loggingDatabase:='logs'}\" \
1921
}" > /usr/share/nginx/html/config.json

src/application/ApplicationActions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,18 @@ export const setStandaloneDashboardDatabase = (dashboardDatabase: string) => ({
172172
payload: { dashboardDatabase },
173173
});
174174

175+
export const SET_LOGGING_MODE = 'APPLICATION/SET_LOGGING_MODE';
176+
export const setLoggingMode = (loggingMode: string) => ({
177+
type: SET_LOGGING_MODE,
178+
payload: { loggingMode },
179+
});
180+
181+
export const SET_LOGGING_DATABASE = 'APPLICATION/SET_LOGGING_DATABASE';
182+
export const setLoggingDatabase = (loggingDatabase: string) => ({
183+
type: SET_LOGGING_DATABASE,
184+
payload: { loggingDatabase },
185+
});
186+
175187
export const SET_SSO_ENABLED = 'APPLICATION/SET_SSO_ENABLED';
176188
export const setSSOEnabled = (enabled: boolean, discoveryUrl: string) => ({
177189
type: SET_SSO_ENABLED,

src/application/ApplicationReducer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
SET_STANDALONE_DASHBOARD_DATEBASE,
2525
SET_STANDALONE_ENABLED,
2626
SET_STANDALONE_MODE,
27+
SET_LOGGING_MODE,
28+
SET_LOGGING_DATABASE,
2729
SET_WAIT_FOR_SSO,
2830
SET_WELCOME_SCREEN_OPEN,
2931
} from './ApplicationActions';
@@ -50,6 +52,7 @@ const initialState = {
5052
dashboardToLoadAfterConnecting: null,
5153
waitForSSO: false,
5254
standalone: false,
55+
loggingMode: '0',
5356
};
5457
export const applicationReducer = (state = initialState, action: { type: any; payload: any }) => {
5558
const { type, payload } = action;
@@ -107,6 +110,16 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
107110
state = update(state, { standalone: standalone });
108111
return state;
109112
}
113+
case SET_LOGGING_MODE: {
114+
const { loggingMode } = payload;
115+
state = update(state, { loggingMode: loggingMode });
116+
return state;
117+
}
118+
case SET_LOGGING_DATABASE: {
119+
const { loggingDatabase } = payload;
120+
state = update(state, { loggingDatabase: loggingDatabase });
121+
return state;
122+
}
110123
case SET_SSO_ENABLED: {
111124
const { enabled, discoveryUrl } = payload;
112125
state = update(state, { ssoEnabled: enabled, ssoDiscoveryUrl: discoveryUrl });

src/application/ApplicationSelectors.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export const applicationGetConnectionDatabase = (state: any) => {
3333
return state.application.connection.database;
3434
};
3535

36+
export const applicationGetConnectionUser = (state: any) => {
37+
return state.application.connection.username;
38+
}
39+
3640
export const applicationGetShareDetails = (state: any) => {
3741
return state.application.shareDetails;
3842
};
@@ -41,6 +45,10 @@ export const applicationIsStandalone = (state: any) => {
4145
return state.application.standalone;
4246
};
4347

48+
export const applicationGetLoggingMode = (state: any) => {
49+
return state.application.loggingMode;
50+
};
51+
4452
export const applicationHasNeo4jDesktopConnection = (state: any) => {
4553
return state.application.desktopConnection != null;
4654
};
@@ -84,6 +92,13 @@ export const applicationGetStandaloneSettings = (state: any) => {
8492
};
8593
};
8694

95+
export const applicationGetLoggingSettings = (state: any) => {
96+
return {
97+
loggingMode: state.application.loggingMode,
98+
loggingDatabase: state.application.loggingDatabase,
99+
};
100+
};
101+
87102
export const applicationHasWelcomeScreenOpen = (state: any) => {
88103
return state.application.welcomeScreenOpen;
89104
};

src/application/ApplicationThunks.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ import {
3434
setAboutModalOpen,
3535
setStandaloneMode,
3636
setStandaloneDashboardDatabase,
37+
setLoggingMode,
38+
setLoggingDatabase,
3739
setWaitForSSO,
3840
setParametersToLoadAfterConnecting,
3941
setReportHelpModalOpen,
4042
} from './ApplicationActions';
4143
import { version } from '../modal/AboutModal';
44+
import { applicationGetLoggingSettings, applicationIsStandalone } from './ApplicationSelectors';
45+
import { createUUID } from '../utils/uuid';
4246

4347
/**
4448
* Application Thunks (https://redux.js.org/usage/writing-logic-thunks) handle complex state manipulations.
@@ -56,6 +60,9 @@ import { version } from '../modal/AboutModal';
5660
*/
5761
export const createConnectionThunk =
5862
(protocol, url, port, database, username, password) => (dispatch: any, getState: any) => {
63+
const loggingState = getState();
64+
const loggingSettings = applicationGetLoggingSettings(loggingState)
65+
const neodashMode = applicationIsStandalone(loggingState) ? 'Standalone' : 'Editor'
5966
try {
6067
const driver = createDriver(protocol, url, port, username, password, { userAgent: `neodash/v${version}` });
6168
// eslint-disable-next-line no-console
@@ -65,13 +72,41 @@ export const createConnectionThunk =
6572
console.log('Confirming connection was established...');
6673
if (records && records[0] && records[0].error) {
6774
dispatch(createNotificationThunk('Unable to establish connection', records[0].error));
68-
} else if (records && records[0] && records[0].keys[0] == 'connected') {
75+
if (loggingSettings.loggingMode>'0'){
76+
dispatch(
77+
createLogThunk(
78+
driver,
79+
loggingSettings.loggingDatabase,
80+
neodashMode,
81+
username,
82+
'ERR - connect to DB',
83+
database,
84+
'',
85+
'Error while trying to establish connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now()
86+
)
87+
);
88+
}
89+
} else if (records && records[0] && records[0].keys[0] == 'connected') {
6990
dispatch(setConnectionProperties(protocol, url, port, database, username, password));
7091
dispatch(setConnectionModalOpen(false));
7192
dispatch(setConnected(true));
7293
dispatch(updateSessionParameterThunk('session_uri', `${protocol}://${url}:${port}`));
7394
dispatch(updateSessionParameterThunk('session_database', database));
7495
dispatch(updateSessionParameterThunk('session_username', username));
96+
if (loggingSettings.loggingMode>'0'){
97+
dispatch(
98+
createLogThunk(
99+
driver,
100+
loggingSettings.loggingDatabase,
101+
neodashMode,
102+
username,
103+
'INF - connect to DB',
104+
database,
105+
'',
106+
username +'established connection to Neo4j DB in ' +neodashMode+' mode at '+Date.now()
107+
)
108+
);
109+
}
75110
// If we have remembered to load a specific dashboard after connecting to the database, take care of it here.
76111
const { application } = getState();
77112
if (
@@ -349,6 +384,8 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
349384
standaloneDashboardName: 'My Dashboard',
350385
standaloneDashboardDatabase: 'dashboards',
351386
standaloneDashboardURL: '',
387+
loggingMode: '0',
388+
loggingDatabase: 'logs',
352389
};
353390
try {
354391
config = await (await fetch('config.json')).json();
@@ -393,6 +430,10 @@ export const loadApplicationConfigThunk = () => async (dispatch: any, getState:
393430
config.standalonePassword
394431
)
395432
);
433+
434+
dispatch(setLoggingMode(config.loggingMode));
435+
dispatch(setLoggingDatabase(config.loggingDatabase));
436+
396437
dispatch(setConnectionModalOpen(false));
397438

398439
// Auto-upgrade the dashboard version if an old version is cached.
@@ -595,3 +636,48 @@ export const initializeApplicationAsStandaloneThunk =
595636
dispatch(setConnectionModalOpen(true));
596637
}
597638
};
639+
640+
// Thunk to handle log events.
641+
export const createLogThunk =
642+
(loggingDriver, loggingDatabase, neodashMode, logUser, logAction, logDatabase, logDashboard = '', logMessage) =>
643+
(dispatch: any) => {
644+
try {
645+
alert('entered log ceation with following parameters:\n'+loggingDriver+'\n'+neodashMode+'\n'+logUser+'\n'+logAction+'\n'+logDatabase+'\n'+logDashboard+'\n'+logMessage)
646+
const uuid = createUUID();
647+
648+
// Generate a cypher query to save the log.
649+
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'
650+
651+
const parameters = {
652+
uuid: uuid,
653+
user: logUser,
654+
logAction: logAction,
655+
logDatabase: logDatabase,
656+
neodashMode: neodashMode,
657+
logDashboard: logDashboard,
658+
logMessage: logMessage
659+
};
660+
runCypherQuery(
661+
loggingDriver,
662+
loggingDatabase,
663+
query,
664+
parameters,
665+
1,
666+
() => {},
667+
(records) => {
668+
if (records && records[0] && records[0]._fields && records[0]._fields[0] && records[0]._fields[0] == uuid) {
669+
dispatch(createNotificationThunk('🎉 Success!', 'log created: '+uuid));
670+
} else {
671+
dispatch(
672+
createNotificationThunk(
673+
'Error creating log',
674+
`Please check logging configuration with your Neodash administrator`
675+
)
676+
);
677+
}
678+
}
679+
);
680+
} catch (e) {
681+
dispatch(createNotificationThunk('Error creating log', e));
682+
}
683+
};

0 commit comments

Comments
 (0)