diff --git a/core/bbs.js b/core/bbs.js index 90c31beb..ebcd0c9d 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -236,6 +236,8 @@ function initialize(cb) { // const User = require('./user.js'); + // :TODO: use User.getUserInfo() for this! + const propLoadOpts = { names : [ UserProps.RealName, UserProps.Sex, UserProps.EmailAddress, @@ -246,7 +248,7 @@ function initialize(cb) { async.waterfall( [ function getOpUserName(next) { - return User.getUserName(1, next); + return User.getUserName(User.RootUserID, next); }, function getOpProps(opUserName, next) { User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => { @@ -273,8 +275,9 @@ function initialize(cb) { } ); }, - function initCallsToday(callback) { + function initSystemLogStats(callback) { const StatLog = require('./stat_log.js'); + const filter = { logName : SysLogKeys.UserLoginHistory, resultType : 'count', @@ -288,7 +291,7 @@ function initialize(cb) { return callback(null); }); }, - function initUserTransferStats(callback) { + function initUserLogStats(callback) { const StatLog = require('./stat_log'); const entries = [ @@ -324,6 +327,33 @@ function initialize(cb) { return callback(null); }); }, + function initLastLogin(callback) { + const StatLog = require('./stat_log'); + StatLog.getSystemLogEntries(SysLogKeys.UserLoginHistory, 'timestamp_desc', 1, (err, lastLogin) => { + if (err) { + return callback(null); + } + + let loginObj; + try + { + loginObj = JSON.parse(lastLogin[0].log_value); + loginObj.timestamp = moment(lastLogin[0].timestamp); + } + catch (e) + { + return callback(null); + } + + // For live stats we want to resolve user ID -> name, etc. + const User = require('./user'); + User.getUserInfo(loginObj.userId, (err, props) => { + const stat = Object.assign({}, props, loginObj); + StatLog.setNonPersistentSystemStat(SysProps.LastLogin, stat); + return callback(null); + }); + }); + }, function initMessageStats(callback) { return require('./message_area.js').startup(callback); }, diff --git a/core/predefined_mci.js b/core/predefined_mci.js index 0842a739..b24918b6 100644 --- a/core/predefined_mci.js +++ b/core/predefined_mci.js @@ -98,7 +98,6 @@ const PREDEFINED_MCI_GENERATORS = { SA : function opAffils() { return StatLog.getSystemStat(SysProps.SysOpAffiliations); }, SS : function opSex() { return StatLog.getSystemStat(SysProps.SysOpSex); }, SE : function opEmail() { return StatLog.getSystemStat(SysProps.SysOpEmailAddress); }, - // :TODO: op age, web, ????? // // Current user / session @@ -243,10 +242,6 @@ const PREDEFINED_MCI_GENERATORS = { UU : function systemUptime() { return moment.duration(process.uptime(), 'seconds').humanize(); }, - - // :TODO: MCI for core count, e.g. os.cpus().length - - // :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage NV : function nodeVersion() { return process.version; }, AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); }, @@ -265,8 +260,6 @@ const PREDEFINED_MCI_GENERATORS = { // // System File Base, Up/Download Info // - // :TODO: DD - Today's # of downloads (iNiQUiTY) - // SD : function systemNumDownloads() { return StatLog.getFriendlySystemStat(SysProps.FileDlTotalCount, 0); }, SO : function systemByteDownload() { const byteSize = StatLog.getSystemStatNum(SysProps.FileDlTotalBytes); @@ -308,10 +301,27 @@ const PREDEFINED_MCI_GENERATORS = { }, // :TODO: NT - New users today (Obv/2) - // :TODO: LC - name of last caller to system (Obv/2) // :TODO: TZ - Average *system* post/call ratio (iNiQUiTY) // :TODO: ?? - Total users on system + LC : function lastCallerUserName() { // Obv/2 + const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; + return lastLogin.userName || 'N/A'; + }, + LD : function lastCallerDate(client) { + const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; + if (!lastLogin.timestamp) { + return 'N/A'; + } + return lastLogin.timestamp.format(client.currentTheme.helpers.getDateFormat()); + }, + LT : function lastCallerTime(client) { + const lastLogin = StatLog.getSystemStat(SysProps.LastLogin) || {}; + if (!lastLogin.timestamp) { + return 'N/A'; + } + return lastLogin.timestamp.format(client.currentTheme.helpers.getTimeFormat()); + }, // // Special handling for XY diff --git a/core/system_property.js b/core/system_property.js index 40c501aa..67049552 100644 --- a/core/system_property.js +++ b/core/system_property.js @@ -10,6 +10,7 @@ module.exports = { LoginCount : 'login_count', LoginsToday : 'logins_today', // non-persistent + LastLogin : 'last_login', // object { userId, sessionId, userName, userRealName, timestamp }; non-persistent FileBaseAreaStats : 'file_base_area_stats', // object - see file_base_area.js::getAreaStats FileUlTotalCount : 'ul_total_count', @@ -25,19 +26,15 @@ module.exports = { MessageTotalCount : 'message_post_total_count', // total non-private messages on the system; non-persistent MessagesToday : 'message_post_today', // non-private messages posted/imported today; non-persistent - // begin +op non-persistent... - SysOpUsername : 'sysop_username', - SysOpRealName : 'sysop_real_name', - SysOpLocation : 'sysop_location', - SysOpAffiliations : 'sysop_affiliation', - SysOpSex : 'sysop_sex', - SysOpEmailAddress : 'sysop_email_address', - // end +op non-persistent + SysOpUsername : 'sysop_username', // non-persistent + SysOpRealName : 'sysop_real_name', // non-persistent + SysOpLocation : 'sysop_location', // non-persistent + SysOpAffiliations : 'sysop_affiliation', // non-persistent + SysOpSex : 'sysop_sex', // non-persistent + SysOpEmailAddress : 'sysop_email_address', // non-persistent NextRandomRumor : 'random_rumor', - // begin system stat non-persistent... - SystemMemoryStats : 'system_memory_stats', // object { totalBytes, freeBytes } - SystemLoadStats : 'system_load_stats', // object { average, current } - // end system stat non persistent + SystemMemoryStats : 'system_memory_stats', // object { totalBytes, freeBytes }; non-persistent + SystemLoadStats : 'system_load_stats', // object { average, current }; non-persistent }; diff --git a/core/user.js b/core/user.js index 7c017e2c..4dad3399 100644 --- a/core/user.js +++ b/core/user.js @@ -630,6 +630,41 @@ module.exports = class User { ); } + static getUserInfo(userId, propsList, cb) { + if (!cb && _.isFunction(propsList)) { + cb = propsList; + propsList = [ + UserProps.RealName, UserProps.Sex, UserProps.EmailAddress, + UserProps.Location, UserProps.Affiliations, + ]; + } + + async.waterfall( + [ + (callback) => { + return User.getUserName(userId, callback); + }, + (userName, callback) => { + User.loadProperties(userId, { names : propsList }, (err, props) => { + return callback(err, Object.assign({}, props, { user_name : userName })); + }); + } + ], + (err, userProps) => { + if (err) { + return cb(err); + } + + const userInfo = {}; + Object.keys(userProps).forEach(key => { + userInfo[_.camelCase(key)] = userProps[key] || 'N/A'; + }); + + return cb(null, userInfo); + } + ); + } + static isRootUserId(userId) { return (User.RootUserID === userId); } diff --git a/core/user_login.js b/core/user_login.js index 3db6b5cc..8c9e41b8 100644 --- a/core/user_login.js +++ b/core/user_login.js @@ -30,6 +30,7 @@ const { const async = require('async'); const _ = require('lodash'); const assert = require('assert'); +const moment = require('moment'); exports.userLogin = userLogin; exports.recordLogin = recordLogin; @@ -176,6 +177,8 @@ function recordLogin(client, cb) { assert(client.user.authenticated); // don't get in situations where this isn't true const user = client.user; + const loginTimestamp = StatLog.now; + async.parallel( [ (callback) => { @@ -183,7 +186,7 @@ function recordLogin(client, cb) { return StatLog.incrementSystemStat(SysProps.LoginCount, 1, callback); }, (callback) => { - return StatLog.setUserStat(user, UserProps.LastLoginTs, StatLog.now, callback); + return StatLog.setUserStat(user, UserProps.LastLoginTs, loginTimestamp, callback); }, (callback) => { return StatLog.incrementUserStat(user, UserProps.LoginCount, 1, callback); @@ -202,6 +205,24 @@ function recordLogin(client, cb) { StatLog.KeepType.Max, callback ); + }, + (callback) => { + // Update live last login information which includes additional + // (pre-resolved) information such as user name/etc. + const lastLogin = { + userId : user.userId, + sessionId : user.sessionId, + userName : user.username, + realName : user.getProperty(UserProps.RealName), + affiliation : user.getProperty(UserProps.Affiliations), + emailAddress : user.getProperty(UserProps.EmailAddress), + sex : user.getProperty(UserProps.Sex), + location : user.getProperty(UserProps.Location), + timestamp : moment(loginTimestamp), + }; + + StatLog.setNonPersistentSystemStat(SysProps.LastLogin, lastLogin); + return callback(null); } ], err => { diff --git a/docs/art/mci.md b/docs/art/mci.md index 235a92a6..c976334a 100644 --- a/docs/art/mci.md +++ b/docs/art/mci.md @@ -100,7 +100,9 @@ There are many predefined MCI codes that can be used anywhere on the system (pla | `LA` | System load average (e.g. 0.25)
(Not available for all platforms) | | `CL` | System current load percentage
(Not available for all platforms) | | `UU` | System uptime in friendly format | - +| `LC` | Last caller to the system (username) | +| `LT` | Time of last caller | +| `LD` | Date of last caller | Some additional special case codes also exist: