From eae865ab8d000a8c5e28843cc35c7893872fe6b4 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 1 Mar 2022 15:25:23 -0800 Subject: [PATCH] Added user login report. --- meshuser.js | 79 +++++++++++++++++++++++++++++++++---- public/images/notify16.png | Bin 0 -> 5914 bytes public/styles/style.css | 9 +++++ views/default.handlebars | 24 +++++++++-- 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 public/images/notify16.png diff --git a/meshuser.js b/meshuser.js index 76f33da2..06301474 100644 --- a/meshuser.js +++ b/meshuser.js @@ -5914,18 +5914,27 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } function serverCommandReport(command) { - if (common.validateInt(command.type, 1, 2) == false) return; // Validate type + if (common.validateInt(command.type, 1, 3) == false) return; // Validate type if (common.validateInt(command.groupBy, 1, 3) == false) return; // Validate groupBy: 1 = User, 2 = Device, 3 = Day if ((typeof command.start != 'number') || (typeof command.end != 'number') || (command.start >= command.end)) return; // Validate start and end time const manageAllDeviceGroups = ((user.siteadmin == 0xFFFFFFFF) && (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0)); if ((command.devGroup != null) && (manageAllDeviceGroups == false) && ((user.links == null) || (user.links[command.devGroup] == null))) return; // Asking for a device group that is not allowed const msgIdFilter = [5, 10, 11, 12, 122, 123, 124, 125, 126]; - - if (command.type == 1) - remoteSessionReport(command, manageAllDeviceGroups, msgIdFilter); - if (command.type == 2) - trafficUsageReport(command, msgIdFilter); + switch (command.type) { + case 1: { + remoteSessionReport(command, manageAllDeviceGroups, msgIdFilter); + break; + } + case 2: { + trafficUsageReport(command, msgIdFilter); + break; + } + case 3: { + userLoginReport(command, msgIdFilter); + break; + } + } } function serverCommandServerClearErrorLog(command) { @@ -6939,7 +6948,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Fetch or create the user entry var userEntry = userEntries[docs[i].userid]; - if (userEntry == null) { userEntry = { userid: docs[i].userid, length: 0, bytesin: 0, bytesout: 0}; } + if (userEntry == null) { userEntry = { userid: docs[i].userid, length: 0, bytesin: 0, bytesout: 0 }; } if (docs[i].bytesin) { userEntry.bytesin += docs[i].bytesin; } if (docs[i].bytesout) { userEntry.bytesout += docs[i].bytesout; } @@ -6959,6 +6968,62 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use }); } + + function userLoginReport(command) { + // If we are not user administrator on this site, only search for events with our own user id. + var ids = [user._id]; // If we are nto user administrator, only count our own traffic. + if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) { ids = ['*']; } // If user administrator, count traffic of all users. + + var showInvalidLoginAttempts = true; + + // Get the events in the time range + // MySQL or MariaDB query will ignore the MsgID filter. + var msgIdFilter = [107]; + if (showInvalidLoginAttempts) { msgIdFilter = [107, 108, 109, 110]; } // Includes invalid login attempts + + db.GetEventsTimeRange(ids, domain.id, msgIdFilter, new Date(command.start * 1000), new Date(command.end * 1000), function (err, docs) { + if (err != null) return; + + // Columns + var data = { groups: {} }; + if (command.groupBy == 1) { + data.groupFormat = 'user'; + data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }]; + } else if (command.groupBy == 3) { + data.columns = [{ id: 'time', title: "time", format: 'time' }, { id: 'userid', title: "user", format: 'user' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }]; + } + if (showInvalidLoginAttempts) { data.columns.push({ id: 'msg', title: "msg", format: 'msg' }); } + + // Add all log entries + var entries = []; + for (var i in docs) { + // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here. + if (msgIdFilter.indexOf(docs[i].msgid) < 0) continue; + + if (command.groupBy == 1) { // Add entry per user + if (data.groups[docs[i].userid] == null) { data.groups[docs[i].userid] = { entries: [] }; } + const entry = { time: docs[i].time.valueOf(), ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2] }; + if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid } + data.groups[docs[i].userid].entries.push(entry); + } else if (command.groupBy == 3) { // Add entry per day + var day; + if ((typeof command.l == 'string') && (typeof command.tz == 'string')) { + day = new Date(docs[i].time).toLocaleDateString(command.l, { timeZone: command.tz }); + } else { + day = docs[i].time; // TODO + } + if (data.groups[day] == null) { data.groups[day] = { entries: [] }; } + const entry = { time: docs[i].time.valueOf(), userid: docs[i].userid, ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2] }; + if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid } + data.groups[day].entries.push(entry); + } + } + + try { ws.send(JSON.stringify({ action: 'report', data: data })); } catch (ex) { } + }); + } + + // Return detailed information about an array of nodeid's function getDeviceDetailedInfo(nodeids, type, func) { if (nodeids == null) { getAllDeviceDetailedInfo(type, func); return; } diff --git a/public/images/notify16.png b/public/images/notify16.png new file mode 100644 index 0000000000000000000000000000000000000000..f0af982c01cc80f7b583981b1acb90988859b603 GIT binary patch literal 5914 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 literal 0 HcmV?d00001 diff --git a/public/styles/style.css b/public/styles/style.css index 1a14d8ec..8b39ed80 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -848,6 +848,15 @@ NoMeshesPanel img { .NotifyIconSmall7 { width:24px; height:24px; background: url(../images/notify24.png) -144px 0px; } .NotifyIconSmall8 { width:24px; height:24px; background: url(../images/notify24.png) -168px 0px; } .NotifyIconSmall9 { width:24px; height:24px; background: url(../images/notify24.png) -192px 0px; } +.NotifyIconTiny1 { width:16px; height:16px; background: url(../images/notify16.png) 0px 0px; } +.NotifyIconTiny2 { width:16px; height:16px; background: url(../images/notify16.png) -16px 0px; } +.NotifyIconTiny3 { width:16px; height:16px; background: url(../images/notify16.png) -32px 0px; } +.NotifyIconTiny4 { width:16px; height:16px; background: url(../images/notify16.png) -48px 0px; } +.NotifyIconTiny5 { width:16px; height:16px; background: url(../images/notify16.png) -64px 0px; } +.NotifyIconTiny6 { width:16px; height:16px; background: url(../images/notify16.png) -80px 0px; } +.NotifyIconTiny7 { width:16px; height:16px; background: url(../images/notify16.png) -96px 0px; } +.NotifyIconTiny8 { width:16px; height:16px; background: url(../images/notify16.png) -112px 0px; } +.NotifyIconTiny9 { width:16px; height:16px; background: url(../images/notify16.png) -128px 0px; } .deviceBatteryLarge { position:absolute; diff --git a/views/default.handlebars b/views/default.handlebars index 283964c4..7f241e66 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -15623,7 +15623,7 @@ if (xxdialogMode) return; var y = '', x = '', settings = JSON.parse(getstore('_ReportSettings', '{}')); - var options = { 1 : "Remote Sessions", 2 : "User Traffic Usage" } + var options = { 1 : "Remote Sessions", 2 : "User Traffic Usage", 3 : "User Logins" } for (var i in options) { y += ''; } x += addHtmlValue("Type", ''); @@ -15634,6 +15634,13 @@ x += addHtmlValue("Group by", ''); x += ''; + x += ''; + x += '