Table output, more fields, and --sort for './oputil.js user list'

This commit is contained in:
Bryan Ashby 2022-08-16 12:19:49 -06:00
parent 5b1c11b1bc
commit b971876795
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
4 changed files with 119 additions and 5 deletions

View File

@ -69,6 +69,18 @@ Actions:
info arguments: info arguments:
--security Include security information in output --security Include security information in output
list arguments:
--sort SORT_BY Specify field to sort by
Valid SORT_BY values:
id : User ID
username : Username
realname : Real name
status : Account status
created : Account creation date
lastlogin : Last login timestamp
logins : Login count
2fa-otp arguments: 2fa-otp arguments:
--qr-type TYPE Specify QR code type --qr-type TYPE Specify QR code type

View File

@ -17,6 +17,7 @@ const async = require('async');
const _ = require('lodash'); const _ = require('lodash');
const moment = require('moment'); const moment = require('moment');
const fs = require('fs-extra'); const fs = require('fs-extra');
const Table = require('easy-table');
exports.handleUserCommand = handleUserCommand; exports.handleUserCommand = handleUserCommand;
@ -340,7 +341,7 @@ function showUserInfo(user) {
const statusDesc = () => { const statusDesc = () => {
const status = user.properties[UserProps.AccountStatus]; const status = user.properties[UserProps.AccountStatus];
return _.invert(User.AccountStatus)[status] || 'unknown'; return _.invert(User.AccountStatus)[status] || 'N/A';
}; };
const created = () => { const created = () => {
@ -508,7 +509,6 @@ function listUsers() {
// :TODO: --created-since SPEC and --last-called SPEC // :TODO: --created-since SPEC and --last-called SPEC
// --created-since SPEC // --created-since SPEC
// SPEC can be TIMESTAMP or e.g. "-1hour" or "-90days" // SPEC can be TIMESTAMP or e.g. "-1hour" or "-90days"
// :TODO: --sort name|id
let listWhat; let listWhat;
if (argv._.length > 2) { if (argv._.length > 2) {
listWhat = argv._[argv._.length - 1]; listWhat = argv._[argv._.length - 1];
@ -516,6 +516,8 @@ function listUsers() {
listWhat = 'all'; listWhat = 'all';
} }
const sortBy = (argv.sort || 'id').toLowerCase();
const User = require('../../core/user'); const User = require('../../core/user');
if (!['all'].concat(Object.keys(User.AccountStatus)).includes(listWhat)) { if (!['all'].concat(Object.keys(User.AccountStatus)).includes(listWhat)) {
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR); return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
@ -527,7 +529,13 @@ function listUsers() {
const UserProps = require('../../core/user_property'); const UserProps = require('../../core/user_property');
const userListOpts = { const userListOpts = {
properties: [UserProps.AccountStatus], properties: [
UserProps.RealName,
UserProps.AccountStatus,
UserProps.AccountCreated,
UserProps.LastLoginTs,
UserProps.LoginCount,
],
}; };
User.getUserList(userListOpts, (err, userList) => { User.getUserList(userListOpts, (err, userList) => {
@ -550,9 +558,93 @@ function listUsers() {
}); });
}, },
(userList, callback) => { (userList, callback) => {
userList.forEach(user => { // default sort: by ID
console.info(`${user.userId}: ${user.userName}`); const sortById = (left, right) => {
return left.userId - right.userId;
};
const sortByLogin = prop => (left, right) => {
return parseInt(right[prop]) - parseInt(left[prop]);
};
const sortByString = prop => (left, right) => {
return left[prop].localeCompare(right[prop], {
sensitivity: false,
numeric: true,
}); });
};
const sortByTimestamp = prop => (left, right) => {
return moment(left[prop]) - moment(right[prop]);
};
let sorter;
switch (sortBy) {
case 'username':
sorter = sortByString('userName');
break;
case 'realname':
sorter = sortByString(UserProps.RealName);
break;
case 'status':
sorter = sortByString(UserProps.AccountStatus);
break;
case 'created':
sorter = sortByTimestamp(UserProps.AccountCreated);
break;
case 'lastlogin':
sorter = sortByTimestamp(UserProps.LastLoginTs);
break;
case 'logins':
sorter = sortByLogin(UserProps.LoginCount);
break;
case 'id':
default:
sorter = sortById;
break;
}
userList.sort(sorter);
const StatusNames = _.invert(User.AccountStatus);
const propOrNA = (user, prop) => {
return user[prop] || 'N/A';
};
const timestampOrNA = (user, prop, format) => {
let ts = user[prop];
return ts ? moment(ts).format(format) : 'N/A';
};
const makeAccountStatus = status => {
return StatusNames[status] || 'N/A';
};
const table = new Table();
userList.forEach(user => {
table.cell('ID', user.userId);
table.cell('Username', user.userName);
table.cell('Real Name', user[UserProps.RealName]);
table.cell(
'Status',
makeAccountStatus(user[UserProps.AccountStatus])
);
table.cell(
'Created',
timestampOrNA(user, UserProps.AccountCreated, 'YYYY-MM-DD')
);
table.cell(
'Last Login',
timestampOrNA(user, UserProps.LastLoginTs, 'YYYY-MM-DD HH::mm')
);
table.cell('Logins', propOrNA(user, UserProps.LoginCount));
table.newRow();
});
console.info(table.toString());
return callback(null); return callback(null);
}, },

View File

@ -59,7 +59,8 @@
"uuid": "8.3.2", "uuid": "8.3.2",
"uuid-parse": "1.1.0", "uuid-parse": "1.1.0",
"ws": "7.4.3", "ws": "7.4.3",
"yazl": "^2.5.1" "yazl": "^2.5.1",
"easy-table": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "8.21.0", "eslint": "8.21.0",

View File

@ -602,6 +602,15 @@ dtrace-provider@~0.8:
dependencies: dependencies:
nan "^2.10.0" nan "^2.10.0"
easy-table@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/easy-table/-/easy-table-1.2.0.tgz#ba9225d7138fee307bfd4f0b5bc3c04bdc7c54eb"
integrity sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==
dependencies:
ansi-regex "^5.0.1"
optionalDependencies:
wcwidth "^1.0.1"
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"