* New StatLog: Replaces various logs, system props, etc. into one class/methods

* Uew StatLog for last callers
* Use new StatLog for +op props
* Use new StatLog for user props such as posts & MCI to access such
* Use StatLog for various new MCI codes for +op
* Misc missing MCI codes
This commit is contained in:
Bryan Ashby 2016-07-27 21:44:27 -06:00
parent d4ce574be3
commit 8787703989
9 changed files with 191 additions and 176 deletions

View File

@ -149,49 +149,58 @@ function initialize(cb) {
function initDatabases(callback) {
database.initializeDatabases(callback);
},
function initSystemProperties(callback) {
require('./system_property.js').loadSystemProperties(callback);
function initStatLog(callback) {
require('./stat_log.js').init(callback);
},
function initThemes(callback) {
// Have to pull in here so it's after Config init
var theme = require('./theme.js');
theme.initAvailableThemes(function onThemesInit(err, themeCount) {
require('./theme.js').initAvailableThemes(function onThemesInit(err, themeCount) {
logger.log.info({ themeCount : themeCount }, 'Themes initialized');
callback(err);
});
},
function loadSysOpInformation(callback) {
function loadSysOpInformation2(callback) {
//
// If user 1 has been created, we have a SysOp. Cache some information
// into Config.
//
var user = require('./user.js'); // must late load
// Copy over some +op information from the user DB -> system propertys.
// * Makes this accessible for MCI codes, easy non-blocking access, etc.
// * We do this every time as the op is free to change this information just
// like any other user
//
const user = require('./user.js');
user.getUserName(1, function unLoaded(err, sysOpUsername) {
if(err) {
callback(null); // non-fatal here
} else {
//
// Load some select properties to cache
//
var propLoadOpts = {
userId : 1,
names : [ 'real_name', 'sex', 'email_address' ],
};
async.waterfall(
[
function getOpUserName(next) {
return user.getUserName(1, next);
},
function getOpProps(opUserName, next) {
const propLoadOpts = {
userId : 1,
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
};
user.loadProperties(propLoadOpts, (err, opProps) => {
return next(err, opUserName, opProps);
});
}
],
(err, opUserName, opProps) => {
const StatLog = require('./stat_log.js');
user.loadProperties(propLoadOpts, function propsLoaded(err, props) {
if(!err) {
conf.config.general.sysOp = {
username : sysOpUsername,
properties : props,
};
if(err) {
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
});
} else {
opProps.username = opUserName;
logger.log.info( { sysOp : conf.config.general.sysOp }, 'System Operator information cached');
}
callback(null); // any error is again, non-fatal here
});
_.each(opProps, (v, k) => {
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
});
}
return callback(null);
}
});
);
},
function readyMessageNetworkSupport(callback) {
require('./msg_network.js').startup(callback);

View File

@ -100,26 +100,36 @@ function createSystemTables() {
dbs.system.run('PRAGMA foreign_keys = ON;');
// Various stat/event logging - see stat_log.js
dbs.system.run(
'CREATE TABLE IF NOT EXISTS system_property (' +
' prop_name VARCHAR PRIMARY KEY NOT NULL,' +
' prop_value VARCHAR NOT NULL' +
');'
);
`CREATE TABLE IF NOT EXISTS system_stat (
stat_name VARCHAR PRIMARY KEY NOT NULL,
stat_value VARCHAR NOT NULL
);`
);
//
// system_log can round log_timestamp for daily, monthly, etc.
// statistics as well as unique entries.
//
/*
dbs.system.run(
'CREATE TABLE IF NOT EXISTS system_log (' +
' log_timestamp DATETIME PRIMARY KEY NOT NULL ( ' +
' log_name VARCHARNOT NULL,' +
' log_value VARCHAR NOT NULL,' +
' UNIQUE(log_timestamp, log_name)' +
');'
);*/
`CREATE TABLE IF NOT EXISTS system_event_log (
id INTEGER PRIMARY KEY,
timestamp DATETIME NOT NULL,
log_name VARCHAR NOT NULL,
log_value VARCHAR NOT NULL,
UNIQUE(timestamp, log_name)
);`
);
dbs.system.run(
`CREATE TABLE IF NOT EXISTS user_event_log (
id INTEGER PRIMARY KEY,
timestamp DATETIME NOT NULL,
user_id INTEGER NOT NULL,
log_name VARCHAR NOT NULL,
log_value VARCHAR NOT NULL,
UNIQUE(timestamp, user_id, log_name)
);`
);
}
function createUserTables() {

View File

@ -2,11 +2,11 @@
'use strict';
var Config = require('./config.js').config;
const StatLog = require('./stat_log.js');
var fs = require('fs');
var paths = require('path');
var _ = require('lodash');
var async = require('async');
var moment = require('moment');
var iconv = require('iconv-lite');
@ -121,7 +121,7 @@ function DropFile(client, fileType) {
moment(up.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
'X:\\GEN\\', // "Path to the GEN directory"
Config.general.sysOp.username, // "Sysop's Name (name BBS refers to Sysop as)"
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
self.client.user.username, // "Alias name"
'00:05', // "Event time (hh:mm)" (note: wat?)
'Y', // "If its an error correcting connection (Y/N)"
@ -143,7 +143,7 @@ function DropFile(client, fileType) {
'0', // "Total Doors Opened"
'0', // "Total Messages Left"
].join('\r\n') + '\r\n', 'cp437');
].join('\r\n') + '\r\n', 'cp437');
};
this.getDoor32Buffer = function() {
@ -178,7 +178,7 @@ function DropFile(client, fileType) {
//
// Note that usernames are just used for first/last names here
//
var opUn = /[^\s]*/.exec(Config.general.sysOp.username)[0];
var opUn = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
var un = /[^\s]*/.exec(self.client.user.username)[0];
var secLevel = self.client.user.getLegacySecurityLevel().toString();

View File

@ -1,6 +1,7 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const ansi = require('../core/ansi_term.js');
@ -10,7 +11,10 @@ const getMessageAreaByTag = require('../core/message_area.js').getMessageAreaB
const updateMessageAreaLastReadId = require('../core/message_area.js').updateMessageAreaLastReadId;
const getUserIdAndName = require('../core/user.js').getUserIdAndName;
const cleanControlCodes = require('../core/string_util.js').cleanControlCodes;
const StatLog = require('./stat_log.js');
// deps
const async = require('async');
const assert = require('assert');
const _ = require('lodash');
@ -295,6 +299,21 @@ function FullScreenEditorModule(options) {
);
};
this.updateUserStats = function(cb) {
if(Message.isPrivateAreaTag(this.message.areaTag)) {
if(cb) {
return cb(null);
}
}
StatLog.incrementUserStat(
self.client.user,
'post_count',
1,
cb
);
};
this.redrawFooter = function(options, cb) {
async.waterfall(
[

View File

@ -2,12 +2,12 @@
'use strict';
// ENiGMA½
const Config = require('./config.js').config;
const Log = require('./logger.js').log;
const getMessageAreaByTag = require('./message_area.js').getMessageAreaByTag;
const getMessageConferenceByTag = require('./message_area.js').getMessageConferenceByTag;
const clientConnections = require('./client_connections.js');
const sysProp = require('./system_property.js');
const Config = require('./config.js').config;
const Log = require('./logger.js').log;
const getMessageAreaByTag = require('./message_area.js').getMessageAreaByTag;
const getMessageConferenceByTag = require('./message_area.js').getMessageConferenceByTag;
const clientConnections = require('./client_connections.js');
const StatLog = require('./stat_log.js');
// deps
const packageJson = require('../package.json');
@ -34,9 +34,14 @@ function getPredefinedMCIValue(client, code) {
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
VN : function version() { return packageJson.version; },
// :TODO: SysOp username
// :TODO: SysOp real name
// +op info
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
// :TODO: op age, web, ?????
//
// Current user / session
@ -60,6 +65,16 @@ function getPredefinedMCIValue(client, code) {
MS : function accountCreated() { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
CS : function currentStatus() { return client.currentStatus; },
PS : function userPostCount() {
const postCount = client.user.properties.post_count || 0;
return postCount.toString();
},
PC : function userPostCallRatio() {
const postCount = client.user.properties.post_count || 0;
const callCount = client.user.properties.login_count;
const ratio = ~~((postCount / callCount) * 100);
return `${ratio}%`;
},
MD : function currentMenuDescription() {
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
@ -73,11 +88,14 @@ function getPredefinedMCIValue(client, code) {
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
return conf ? conf.name : '';
},
ML : function messageAreaDescription() {
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
return area ? area.desc : '';
},
CM : function messageConfDescription() {
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
return conf ? conf.desc : '';
},
SH : function termHeight() { return client.term.termHeight.toString(); },
SW : function termWidth() { return client.term.termWidth.toString(); },
@ -103,7 +121,7 @@ function getPredefinedMCIValue(client, code) {
},
OA : function systemArchitecture() { return os.arch(); },
SC : function systemCpuModel() {
SC : function systemCpuModel() {
//
// Clean up CPU strings a bit for better display
//
@ -114,11 +132,11 @@ function getPredefinedMCIValue(client, code) {
// :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
// :TODO: Node version/info
NV : function nodeVersion() { return process.version; },
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); },
TC : function totalCalls() { return sysProp.getSystemProperty('login_count').toString(); },
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toString(); },
//
// Special handling for XY

View File

@ -358,28 +358,6 @@ User.prototype.persistAllProperties = function(cb) {
assert(this.userId > 0);
this.persistProperties(this.properties, cb);
/*
var self = this;
var stmt = userDb.prepare(
'REPLACE INTO user_property (user_id, prop_name, prop_value) ' +
'VALUES (?, ?, ?);');
async.each(Object.keys(this.properties), function property(propName, callback) {
stmt.run(self.userId, propName, self.properties[propName], function onRun(err) {
callback(err);
});
}, function complete(err) {
if(err) {
cb(err);
} else {
stmt.finalize(function finalized() {
cb(null);
});
}
});
*/
};
User.prototype.setNewAuthCredentials = function(password, cb) {

View File

@ -1,16 +1,15 @@
/* jslint node: true */
'use strict';
var setClientTheme = require('./theme.js').setClientTheme;
var clientConnections = require('./client_connections.js').clientConnections;
var userDb = require('./database.js').dbs.user;
var sysProp = require('./system_property.js');
var logger = require('./logger.js');
var Config = require('./config.js').config;
// ENiGMA½
const setClientTheme = require('./theme.js').setClientTheme;
const clientConnections = require('./client_connections.js').clientConnections;
const userDb = require('./database.js').dbs.user;
const StatLog = require('./stat_log.js');
const logger = require('./logger.js');
var async = require('async');
var _ = require('lodash');
var assert = require('assert');
// deps
const async = require('async');
exports.userLogin = userLogin;
@ -24,8 +23,8 @@ function userLogin(client, username, password, cb) {
cb(err);
} else {
var now = new Date();
var user = client.user;
const now = new Date();
const user = client.user;
//
// Ensure this user is not already logged in.
@ -60,46 +59,21 @@ function userLogin(client, username, password, cb) {
async.parallel(
[
function setTheme(callback) {
setClientTheme(client, user.properties.theme_id);
callback(null);
},
function setTheme(callback) {
setClientTheme(client, user.properties.theme_id);
callback(null);
},
function updateSystemLoginCount(callback) {
var sysLoginCount = sysProp.getSystemProperty('login_count') || 0;
sysLoginCount = parseInt(sysLoginCount, 10) + 1;
sysProp.persistSystemProperty('login_count', sysLoginCount, callback);
StatLog.incrementSystemStat('login_count', 1, callback);
},
function recordLastLogin(callback) {
user.persistProperty('last_login_timestamp', now.toISOString(), function persisted(err) {
callback(err);
});
StatLog.setUserStat(user, 'last_login_timestamp', StatLog.now, callback);
},
function updateUserLoginCount(callback) {
if(!user.properties.login_count) {
user.properties.login_count = 1;
} else {
user.properties.login_count++;
}
user.persistProperty('login_count', user.properties.login_count, function persisted(err) {
callback(err);
});
StatLog.incrementUserStat(user, 'login_count', 1, callback);
},
function recordLoginHistory(callback) {
userDb.serialize(function serialized() {
userDb.run(
'INSERT INTO user_login_history (user_id, user_name, timestamp) ' +
'VALUES(?, ?, ?);', [ user.userId, user.username, now.toISOString() ]
);
// keep 30 days of records
userDb.run(
'DELETE FROM user_login_history ' +
'WHERE timestamp <= DATETIME("now", "-30 day");'
);
});
callback(null);
StatLog.appendSystemLogEntry('user_login_history', user.userId, 30, callback);
}
],
function complete(err) {

View File

@ -1,15 +1,17 @@
/* jslint node: true */
'use strict';
var MenuModule = require('../core/menu_module.js').MenuModule;
var userDb = require('../core/database.js').dbs.user;
var ViewController = require('../core/view_controller.js').ViewController;
var getSystemLoginHistory = require('../core/stats.js').getSystemLoginHistory;
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const StatLog = require('../core/stat_log.js');
const getUserName = require('../core/user.js').getUserName;
const loadProperties = require('../core/user.js').loadProperties;
var moment = require('moment');
var async = require('async');
var assert = require('assert');
var _ = require('lodash');
// deps
const moment = require('moment');
const async = require('async');
const _ = require('lodash');
/*
Available listFormat object members:
@ -41,11 +43,11 @@ function LastCallersModule(options) {
require('util').inherits(LastCallersModule, MenuModule);
LastCallersModule.prototype.mciReady = function(mciData, cb) {
var self = this;
var vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
var loginHistory;
var callersView;
let loginHistory;
let callersView;
async.series(
[
@ -53,7 +55,7 @@ LastCallersModule.prototype.mciReady = function(mciData, cb) {
LastCallersModule.super_.prototype.mciReady.call(self, mciData, callback);
},
function loadFromConfig(callback) {
var loadOpts = {
const loadOpts = {
callingMenu : self,
mciMap : mciData.menu,
noInput : true,
@ -64,51 +66,53 @@ LastCallersModule.prototype.mciReady = function(mciData, cb) {
function fetchHistory(callback) {
callersView = vc.getView(MciCodeIds.CallerList);
getSystemLoginHistory(callersView.dimens.height, function historyRetrieved(err, lh) {
StatLog.getSystemLogEntries('user_login_history', 'timestamp_desc', callersView.dimens.height, (err, lh) => {
loginHistory = lh;
callback(err);
return callback(err);
});
},
function fetchUserProperties(callback) {
async.each(loginHistory, function entry(histEntry, next) {
userDb.each(
'SELECT prop_name, prop_value ' +
'FROM user_property ' +
'WHERE user_id=? AND (prop_name="location" OR prop_name="affiliation");',
[ histEntry.userId ],
function propRow(err, propEntry) {
histEntry[propEntry.prop_name] = propEntry.prop_value;
},
function complete(err) {
next();
}
);
}, function complete(err) {
callback(err);
});
function getUserNamesAndProperties(callback) {
const getPropOpts = {
names : [ 'location', 'affiliation' ]
};
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
async.each(
loginHistory,
(item, next) => {
item.userId = parseInt(item.log_value);
item.ts = moment(item.timestamp).format(dateTimeFormat);
getUserName(item.userId, (err, userName) => {
item.userName = userName;
getPropOpts.userId = item.userId;
loadProperties(getPropOpts, (err, props) => {
if(!err) {
item.location = props.location;
item.affiliation = item.affils = props.affiliation;
}
return next();
});
});
},
callback
);
},
function populateList(callback) {
var listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affils} - {ts}';
var dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affils} - {ts}';
callersView.setItems(_.map(loginHistory, function formatCallEntry(ce) {
return listFormat.format({
userId : ce.userId,
userName : ce.userName,
ts : moment(ce.timestamp).format(dateTimeFormat),
location : ce.location,
affils : ce.affiliation,
});
}));
callersView.setItems(_.map(loginHistory, ce => listFormat.format(ce) ) );
// :TODO: This is a hack until pipe codes are better implemented
callersView.focusItems = callersView.items;
callersView.redraw();
callback(null);
return callback(null);
}
],
function complete(err) {
(err) => {
if(err) {
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
}

View File

@ -38,6 +38,9 @@ function AreaPostFSEModule(options) {
},
function saveMessage(callback) {
return persistMessage(msg, callback);
},
function updateStats(callback) {
self.updateUserStats(callback);
}
],
function complete(err) {