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: