add reports to meshctrl

The only format that makes sense to me is CSV, node and mesh ids in the sessions report are just too big for terminal view and JSON is inefficient (too much white-space). The first field (group) derives from the groupby arguement and is "0" when no grouping is applicable (e.g. db reports). resolves #3472, resolves #3509

John Rallis committed Mar 30, 2023 at 15:07 UTC f3d93c882fb6405d30d07e934328c69955379cc7
1 file changed +65 -1
meshctrl.js
+65 -1
@@ -16,7 +16,7 @@ var settings = {};
16 const crypto = require('crypto');
17 const args = require('minimist')(process.argv.slice(2));
18 const path = require('path');
19 -const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload'];
19 +const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report'];
20 if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
21
22 if (args['_'].length == 0) {
@@ -69,6 +69,7 @@ if (args['_'].length == 0) {
69 console.log(" DevicePower - Perform wake/sleep/reset/off operations on remote devices.");
70 console.log(" DeviceSharing - View, add and remove sharing links for a given device.");
71 console.log(" AgentDownload - Download an agent of a specific type for a device group.");
72 + console.log(" Report - Create and show a CSV report.");
73 console.log("\r\nSupported login arguments:");
74 console.log(" --url [wss://server] - Server url, wss://localhost:443 is default.");
75 console.log(" - Use wss://localhost:443?key=xxx if login key is required.");
@@ -275,6 +276,11 @@ if (args['_'].length == 0) {
276 else { ok = true; }
277 break;
278 }
279 + case 'report': {
280 + if (args.type == null) { console.log(winRemoveSingleQuotes("Missing report type, use --type '[reporttype]'")); }
281 + else { ok = true; }
282 + break;
283 + }
284 case 'help': {
285 if (args['_'].length < 2) {
286 console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.');
@@ -961,6 +967,20 @@ if (args['_'].length == 0) {
967 console.log(" --title [title] - Toast title, default is \"MeshCentral\".");
968 break;
969 }
970 + case 'report': {
971 + console.log("Generate a CSV report, Example usages:\r\n");
972 + console.log(" MeshCtrl Report --type sessions --devicegroup mesh//...");
973 + console.log(" MeshCtrl Report --type traffic --json");
974 + console.log(" MeshCtrl Report --type logins --groupby day");
975 + console.log(" MeshCtrl Report --type db");
976 + console.log("\r\nOptional arguments:\r\n");
977 + console.log(" --start [yyyy-mm-ddThh:mm:ss] - Filter the results starting at that date. Defaults to last 24h and last week when used with --groupby day. Usable with sessions, traffic and logins");
978 + console.log(" --end [yyyy-mm-ddThh:mm:ss] - Filter the results ending at that date. Defaults to now. Usable with sessions, traffic and logins");
979 + console.log(" --groupby [name] - How to group results. Options: user, day, device. Defaults to user. User and day usable in sessions and logins, device usable in sessions.");
980 + console.log(" --devicegroup [devicegroupid] - Filter the results by device group. Usable in sessions");
981 + console.log(" --showtraffic - Add traffic data in sessions report");
982 + break;
983 + }
984 default: {
985 console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.');
986 }
@@ -1683,6 +1703,41 @@ function serverConnect() {
1703 ws.send(JSON.stringify({ action: 'toast', nodeids: [args.id], title: args.title ? args.title : "MeshCentral", msg: args.msg, responseid: 'meshctrl' }));
1704 break;
1705 }
1706 + case 'report': {
1707 + var reporttype = 1;
1708 + switch(args.type) {
1709 + case 'traffic':
1710 + reporttype = 2;
1711 + break;
1712 + case 'logins':
1713 + reporttype = 3;
1714 + break;
1715 + case 'db':
1716 + reporttype = 4;
1717 + break;
1718 + }
1719 +
1720 + var reportgroupby = 1;
1721 + if(args.groupby){
1722 + reportgroupby = args.groupby === 'device' ? 2 : args.groupby === 'day' ? 3: 1;
1723 + }
1724 +
1725 + var start = null, end = null;
1726 + if (args.start) {
1727 + start = Math.floor(Date.parse(args.start) / 1000);
1728 + } else {
1729 + start = reportgroupby === 3 ? Math.round(new Date().getTime() / 1000) - (168 * 3600) : Math.round(new Date().getTime() / 1000) - (24 * 3600);
1730 + }
1731 + if (args.end) {
1732 + end = Math.floor(Date.parse(args.end) / 1000);
1733 + } else {
1734 + end = Math.round(new Date().getTime() / 1000);
1735 + }
1736 + if (end <= start) { console.log("End time must be ahead of start time."); process.exit(1); return; }
1737 +
1738 + ws.send(JSON.stringify({ action: 'report', type: reporttype, groupBy: reportgroupby, devGroup: args.devicegroup || null, start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, tf: new Date().getTimezoneOffset(), showTraffic: args.hasOwnProperty('showtraffic'), l: 'en', responseid: 'meshctrl' }));
1739 + break;
1740 + }
1741 }
1742 });
1743
@@ -2249,6 +2304,15 @@ function serverConnect() {
2304 console.log(data.data);
2305 process.exit();
2306 }
2307 + case 'report': {
2308 + console.log('group,' + data.data.columns.flatMap(c => c.id).join(','));
2309 + Object.keys(data.data.groups).forEach(gk => {
2310 + data.data.groups[gk].entries.forEach(e => {
2311 + console.log(gk + ',' + Object.values(e).join(','));
2312 + });
2313 + });
2314 + process.exit();
2315 + }
2316 default: { break; }
2317 }
2318 //console.log('Data', data);