* 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:
parent
d4ce574be3
commit
8787703989
65
core/bbs.js
65
core/bbs.js
|
@ -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.
|
||||
// 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
|
||||
//
|
||||
var user = require('./user.js'); // must late load
|
||||
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 = {
|
||||
async.waterfall(
|
||||
[
|
||||
function getOpUserName(next) {
|
||||
return user.getUserName(1, next);
|
||||
},
|
||||
function getOpProps(opUserName, next) {
|
||||
const propLoadOpts = {
|
||||
userId : 1,
|
||||
names : [ 'real_name', 'sex', 'email_address' ],
|
||||
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
||||
};
|
||||
|
||||
user.loadProperties(propLoadOpts, function propsLoaded(err, props) {
|
||||
if(!err) {
|
||||
conf.config.general.sysOp = {
|
||||
username : sysOpUsername,
|
||||
properties : props,
|
||||
};
|
||||
|
||||
logger.log.info( { sysOp : conf.config.general.sysOp }, 'System Operator information cached');
|
||||
}
|
||||
callback(null); // any error is again, non-fatal here
|
||||
user.loadProperties(propLoadOpts, (err, opProps) => {
|
||||
return next(err, opUserName, opProps);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, opUserName, opProps) => {
|
||||
const StatLog = require('./stat_log.js');
|
||||
|
||||
if(err) {
|
||||
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
||||
});
|
||||
} else {
|
||||
opProps.username = opUserName;
|
||||
|
||||
_.each(opProps, (v, k) => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
||||
});
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
function readyMessageNetworkSupport(callback) {
|
||||
require('./msg_network.js').startup(callback);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)"
|
||||
|
@ -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();
|
||||
|
||||
|
|
19
core/fse.js
19
core/fse.js
|
@ -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(
|
||||
[
|
||||
|
|
|
@ -7,7 +7,7 @@ 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 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(); },
|
||||
|
@ -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
|
||||
|
|
22
core/user.js
22
core/user.js
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
@ -65,41 +64,16 @@ function userLogin(client, username, password, cb) {
|
|||
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) {
|
||||
|
|
|
@ -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 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;
|
||||
}
|
||||
);
|
||||
}, function complete(err) {
|
||||
callback(err);
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ function AreaPostFSEModule(options) {
|
|||
},
|
||||
function saveMessage(callback) {
|
||||
return persistMessage(msg, callback);
|
||||
},
|
||||
function updateStats(callback) {
|
||||
self.updateUserStats(callback);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
|
|
Loading…
Reference in New Issue