New MCI codes & user status flags support additions

* New MCI and WFC properties for user new private and "addressed to" mail
* Additional support for user status flags in connection lists, etc.
This commit is contained in:
Bryan Ashby 2022-05-11 20:30:25 -06:00
parent 6502f3b55e
commit 868e14aa8e
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
16 changed files with 135 additions and 18 deletions

View File

@ -248,10 +248,11 @@
mainMenuWaitingForCaller: { mainMenuWaitingForCaller: {
config: { config: {
quickLogTimestampFormat: "|01|02MM|08/|02DD hh:mm:ssa"
nowDateTimeFormat: "|00|11dddd|08, |11MMMM Do YYYY |08/ |11h|08:|11mm|08:|11ss|03a" nowDateTimeFormat: "|00|11dddd|08, |11MMMM Do YYYY |08/ |11h|08:|11mm|08:|11ss|03a"
lastLoginDateTimeFormat: "|00|10ddd hh|08:|10mm|02a" lastLoginDateTimeFormat: "|00|10ddd hh|08:|10mm|02a"
mainInfoFormat10: "|00|11{now} {currentUserName} |08- |03mail|08: " mainInfoFormat10: "|00|11{now} {currentUserName} |08- |03mail|08: |11{newPrivateMail}|03 prv|08, |11{newMessagesAddrTo}|03 addr to"
mainInfoFormat11: "|00|10{callsToday:>5}" mainInfoFormat11: "|00|10{callsToday:>5}"
mainInfoFormat12: "|00|10{postsToday:>5}" mainInfoFormat12: "|00|10{postsToday:>5}"
mainInfoFormat13: "|00|10{uploadsToday:>2} |08/ |10{uploadBytesToday!sizeWithoutAbbr} |02{uploadBytesToday!sizeAbbr}" mainInfoFormat13: "|00|10{uploadsToday:>2} |08/ |10{uploadBytesToday!sizeWithoutAbbr} |02{uploadBytesToday!sizeAbbr}"

View File

@ -163,6 +163,7 @@ exports.getModule = class AbracadabraModule extends MenuModule {
this.client.term.write(ansi.resetScreen()); this.client.term.write(ansi.resetScreen());
const exeInfo = { const exeInfo = {
name : this.config.name,
cmd : this.config.cmd, cmd : this.config.cmd,
cwd : this.config.cwd || paths.dirname(this.config.cmd), cwd : this.config.cwd || paths.dirname(this.config.cmd),
args : this.config.args, args : this.config.args,

View File

@ -141,7 +141,7 @@ function shutdownSystem() {
[ [
function closeConnections(callback) { function closeConnections(callback) {
const ClientConns = require('./client_connections.js'); const ClientConns = require('./client_connections.js');
const activeConnections = ClientConns.getActiveConnections(); const activeConnections = ClientConns.getActiveConnections(ClientConns.AllConnections);
let i = activeConnections.length; let i = activeConnections.length;
while(i--) { while(i--) {
const activeTerm = activeConnections[i].term; const activeTerm = activeConnections[i].term;

View File

@ -21,7 +21,16 @@ exports.getConnectionByNodeId = getConnectionByNodeId;
const clientConnections = []; const clientConnections = [];
exports.clientConnections = clientConnections; exports.clientConnections = clientConnections;
function getActiveConnections(options = { authUsersOnly: true, visibleOnly: true }) { const AllConnections = { authUsersOnly: false, visibleOnly: false, availOnly: false };
exports.AllConnections = AllConnections;
const UserVisibleConnections = { authUsersOnly: false, visibleOnly: true, availOnly: false };
exports.UserVisibleConnections = UserVisibleConnections;
const UserMessageableConnections = { authUsersOnly: true, visibleOnly: true, availOnly: true };
exports.UserMessageableConnections = UserMessageableConnections;
function getActiveConnections(options = { authUsersOnly: true, visibleOnly: true, availOnly: false }) {
return clientConnections.filter(conn => { return clientConnections.filter(conn => {
if (options.authUsersOnly && !conn.user.isAuthenticated()) { if (options.authUsersOnly && !conn.user.isAuthenticated()) {
return false; return false;
@ -29,13 +38,15 @@ function getActiveConnections(options = { authUsersOnly: true, visibleOnly: true
if (options.visibleOnly && !conn.user.isVisible()) { if (options.visibleOnly && !conn.user.isVisible()) {
return false; return false;
} }
if (options.availOnly && !conn.user.isAvailable()) {
return false;
}
return true; return true;
//return ((options.authUsersOnly && conn.user.isAuthenticated()) || !options.authUsersOnly);
}); });
} }
function getActiveConnectionList(options = { authUsersOnly: true, visibleOnly: true }) { function getActiveConnectionList(options = { authUsersOnly: true, visibleOnly: true, availOnly: false }) {
const now = moment(); const now = moment();
return _.map(getActiveConnections(options), ac => { return _.map(getActiveConnections(options), ac => {
@ -149,9 +160,9 @@ function removeClient(client) {
} }
function getConnectionByUserId(userId) { function getConnectionByUserId(userId) {
return getActiveConnections().find( ac => userId === ac.user.userId ); return getActiveConnections(AllConnections).find( ac => userId === ac.user.userId );
} }
function getConnectionByNodeId(nodeId) { function getConnectionByNodeId(nodeId) {
return getActiveConnections().find( ac => nodeId == ac.node ); return getActiveConnections(AllConnections).find( ac => nodeId == ac.node );
} }

View File

@ -74,7 +74,7 @@ module.exports = class Door {
this.client.log.info( this.client.log.info(
{ cmd : exeInfo.cmd, args, io : this.io }, { cmd : exeInfo.cmd, args, io : this.io },
'Executing external door process' `Executing external door (${exeInfo.name})`
); );
try { try {

View File

@ -598,7 +598,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) { if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
textView.addText(text); textView.addText(text);
} else { } else if (textView.getData() != text) {
textView.setText(text); textView.setText(text);
} }
} }

View File

@ -40,6 +40,7 @@ exports.filterMessageListByReadACS = filterMessageListByReadACS;
exports.tempChangeMessageConfAndArea = tempChangeMessageConfAndArea; exports.tempChangeMessageConfAndArea = tempChangeMessageConfAndArea;
exports.getMessageListForArea = getMessageListForArea; exports.getMessageListForArea = getMessageListForArea;
exports.getNewMessageCountInAreaForUser = getNewMessageCountInAreaForUser; exports.getNewMessageCountInAreaForUser = getNewMessageCountInAreaForUser;
exports.getNewMessageCountAddressedToUser = getNewMessageCountAddressedToUser;
exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser; exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser;
exports.getMessageIdNewerThanTimestampByArea = getMessageIdNewerThanTimestampByArea; exports.getMessageIdNewerThanTimestampByArea = getMessageIdNewerThanTimestampByArea;
exports.getMessageAreaLastReadId = getMessageAreaLastReadId; exports.getMessageAreaLastReadId = getMessageAreaLastReadId;
@ -489,6 +490,26 @@ function getNewMessageCountInAreaForUser(userId, areaTag, cb) {
}); });
} }
// New message count -- for all areas available to the user
// that are addressed to that user (ie: matching username)
// Does NOT Include private messages.
function getNewMessageCountAddressedToUser(client, cb) {
const areaTags = getAllAvailableMessageAreaTags(client).filter(areaTag => areaTag !== Message.WellKnownAreaTags.Private);
let newMessageCount = 0;
async.forEach(areaTags, (areaTag, nextAreaTag) => {
getMessageAreaLastReadId(client.user.userId, areaTag, (_, lastMessageId) => {
lastMessageId = lastMessageId || 0;
getNewMessageCountInAreaForUser(client.user.userId, areaTag, (err, count) => {
newMessageCount += count;
return nextAreaTag(err);
});
});
}, () => {
return cb(null, newMessageCount);
});
}
function getNewMessagesInAreaForUser(userId, areaTag, cb) { function getNewMessagesInAreaForUser(userId, areaTag, cb) {
getMessageAreaLastReadId(userId, areaTag, (err, lastMessageId) => { getMessageAreaLastReadId(userId, areaTag, (err, lastMessageId) => {
lastMessageId = lastMessageId || 0; lastMessageId = lastMessageId || 0;
@ -509,6 +530,7 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
}); });
} }
function getMessageListForArea(client, areaTag, filter, cb) function getMessageListForArea(client, areaTag, filter, cb)
{ {
if(!cb && _.isFunction(filter)) { if(!cb && _.isFunction(filter)) {

View File

@ -6,6 +6,7 @@ const { MenuModule } = require('./menu_module.js');
const { const {
getActiveConnectionList, getActiveConnectionList,
getConnectionByNodeId, getConnectionByNodeId,
UserMessageableConnections,
} = require('./client_connections.js'); } = require('./client_connections.js');
const UserInterruptQueue = require('./user_interrupt_queue.js'); const UserInterruptQueue = require('./user_interrupt_queue.js');
const { getThemeArt } = require('./theme.js'); const { getThemeArt } = require('./theme.js');
@ -204,7 +205,7 @@ exports.getModule = class NodeMessageModule extends MenuModule {
location : 'N/A', location : 'N/A',
affils : 'N/A', affils : 'N/A',
timeOn : 'N/A', timeOn : 'N/A',
}].concat(getActiveConnectionList() }].concat(getActiveConnectionList(UserMessageableConnections)
.map(node => Object.assign(node, { text : -1 == node.node ? '-ALL-' : node.node.toString() } )) .map(node => Object.assign(node, { text : -1 == node.node ? '-ALL-' : node.node.toString() } ))
).filter(node => node.node !== this.client.node); // remove our client's node ).filter(node => node.node !== this.client.node); // remove our client's node
this.nodeList.sort( (a, b) => a.node - b.node ); // sort by node this.nodeList.sort( (a, b) => a.node - b.node ); // sort by node

View File

@ -186,6 +186,12 @@ const PREDEFINED_MCI_GENERATORS = {
const minutes = client.user.properties[UserProps.MinutesOnlineTotalCount] || 0; const minutes = client.user.properties[UserProps.MinutesOnlineTotalCount] || 0;
return moment.duration(minutes, 'minutes').humanize(); return moment.duration(minutes, 'minutes').humanize();
}, },
NM : function userNewMessagesAddressedToCount(client) {
return StatLog.getUserStatNumByClient(client, UserProps.NewAddressedToMessageCount);
},
NP : function userNewPrivateMailCount(client) {
return StatLog.getUserStatNumByClient(client, UserProps.NewPrivateMailCount);
},
// //
// Date/Time // Date/Time
@ -243,7 +249,7 @@ const PREDEFINED_MCI_GENERATORS = {
return moment.duration(process.uptime(), 'seconds').humanize(); return moment.duration(process.uptime(), 'seconds').humanize();
}, },
NV : function nodeVersion() { return process.version; }, NV : function nodeVersion() { return process.version; },
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); }, AN : function activeNodes() { return clientConnections.getActiveConnections(clientConnections.UserVisibleConnections).length.toString(); },
TC : function totalCalls() { return StatLog.getSystemStat(SysProps.LoginCount).toLocaleString(); }, TC : function totalCalls() { return StatLog.getSystemStat(SysProps.LoginCount).toLocaleString(); },
TT : function totalCallsToday() { TT : function totalCallsToday() {

View File

@ -7,6 +7,8 @@ const {
} = require('./database.js'); } = require('./database.js');
const Errors = require('./enig_error.js'); const Errors = require('./enig_error.js');
const SysProps = require('./system_property.js'); const SysProps = require('./system_property.js');
const UserProps = require('./user_property');
const Message = require('./message');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -152,13 +154,25 @@ class StatLog {
} }
getUserStat(user, statName) { getUserStat(user, statName) {
return user.properties[statName]; return user.getProperty(statName);
}
getUserStatByClient(client, statName) {
const stat = this.getUserStat(client.user, statName);
this._refreshUserStat(client, statName);
return stat;
} }
getUserStatNum(user, statName) { getUserStatNum(user, statName) {
return parseInt(this.getUserStat(user, statName)) || 0; return parseInt(this.getUserStat(user, statName)) || 0;
} }
getUserStatNumByClient(client, statName, ttlSeconds=10) {
const stat = this.getUserStatNum(client.user, statName);
this._refreshUserStat(client, statName, ttlSeconds);
return stat;
}
incrementUserStat(user, statName, incrementBy, cb) { incrementUserStat(user, statName, incrementBy, cb) {
incrementBy = incrementBy || 1; incrementBy = incrementBy || 1;
@ -391,6 +405,49 @@ class StatLog {
}); });
} }
_refreshUserStat(client, statName, ttlSeconds) {
switch(statName) {
case UserProps.NewPrivateMailCount:
this._wrapUserRefreshWithCachedTTL(client, statName, this._refreshUserPrivateMailCount, ttlSeconds);
break;
case UserProps.NewAddressedToMessageCount:
this._wrapUserRefreshWithCachedTTL(client, statName, this._refreshUserNewAddressedToMessageCount, ttlSeconds);
break;
}
}
_wrapUserRefreshWithCachedTTL(client, statName, updateMethod, ttlSeconds) {
client.statLogRefreshCache = client.statLogRefreshCache || new Map();
const now = Math.floor(Date.now() / 1000);
const old = client.statLogRefreshCache.get(statName) || 0;
if (now < old + ttlSeconds) {
return;
}
updateMethod(client);
client.statLogRefreshCache.set(statName, now);
}
_refreshUserPrivateMailCount(client) {
const MsgArea = require('./message_area');
MsgArea.getNewMessageCountInAreaForUser(client.user.userId, Message.WellKnownAreaTags.Private, (err, count) => {
if (!err) {
client.user.setProperty(UserProps.NewPrivateMailCount, count);
}
});
}
_refreshUserNewAddressedToMessageCount(client) {
const MsgArea = require('./message_area');
MsgArea.getNewMessageCountAddressedToUser(client, (err, count) => {
if(!err) {
client.user.setProperty(UserProps.NewAddressedToMessageCount, count);
}
});
}
_findLogEntries(logTable, filter, cb) { _findLogEntries(logTable, filter, cb) {
filter = filter || {}; filter = filter || {};
if(!_.isString(filter.logName)) { if(!_.isString(filter.logName)) {

View File

@ -29,6 +29,11 @@ module.exports = class UserInterruptQueue
omitNodes = [ opts.omit ]; omitNodes = [ opts.omit ];
} }
omitNodes = omitNodes.map(n => _.isNumber(n) ? n : n.node); omitNodes = omitNodes.map(n => _.isNumber(n) ? n : n.node);
const connOpts = {
authUsersOnly: true,
visibleOnly: true,
availOnly: true,
};
opts.clients = getActiveConnections(true).filter(ac => !omitNodes.includes(ac.node)); opts.clients = getActiveConnections(true).filter(ac => !omitNodes.includes(ac.node));
} }
if(!Array.isArray(opts.clients)) { if(!Array.isArray(opts.clients)) {

View File

@ -65,5 +65,7 @@ module.exports = {
AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA
AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes
NewPrivateMailCount : 'new_private_mail_count', // non-persistent
NewAddressedToMessageCount : 'new_addr_to_msg_count', // non-persistent
}; };

View File

@ -1,7 +1,10 @@
// ENiGMA½ // ENiGMA½
const { MenuModule } = require('./menu_module'); const { MenuModule } = require('./menu_module');
const { getActiveConnectionList } = require('./client_connections'); const {
getActiveConnectionList,
AllConnections
} = require('./client_connections');
const StatLog = require('./stat_log'); const StatLog = require('./stat_log');
const SysProps = require('./system_property'); const SysProps = require('./system_property');
const UserProps = require('./user_property'); const UserProps = require('./user_property');
@ -36,6 +39,7 @@ const MciViewIds = {
// Secure + 2FA + root user + 'wfc' group. // Secure + 2FA + root user + 'wfc' group.
const DefaultACS = 'SCAF2ID1GM[wfc]'; const DefaultACS = 'SCAF2ID1GM[wfc]';
const MainStatRefreshTimeMs = 5000; // 5s const MainStatRefreshTimeMs = 5000; // 5s
const MailCountTTLSeconds = 10;
exports.getModule = class WaitingForCallerModule extends MenuModule { exports.getModule = class WaitingForCallerModule extends MenuModule {
constructor(options) { constructor(options) {
@ -217,11 +221,12 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
lastLoginDate : moment(lastLoginStats.timestamp).format(this.getDateFormat()), lastLoginDate : moment(lastLoginStats.timestamp).format(this.getDateFormat()),
lastLoginTime : moment(lastLoginStats.timestamp).format(this.getTimeFormat()), lastLoginTime : moment(lastLoginStats.timestamp).format(this.getTimeFormat()),
lastLogin : moment(lastLoginStats.timestamp).format(this._dateTimeFormat('lastLogin')), lastLogin : moment(lastLoginStats.timestamp).format(this._dateTimeFormat('lastLogin')),
totalMemoryBytes : sysMemStats.totalBytes || 0, totalMemoryBytes : sysMemStats.totalBytes || 0,
freeMemoryBytes : sysMemStats.freeBytes || 0, freeMemoryBytes : sysMemStats.freeBytes || 0,
systemAvgLoad : sysLoadStats.average || 0, systemAvgLoad : sysLoadStats.average || 0,
systemCurrentLoad : sysLoadStats.current || 0, systemCurrentLoad : sysLoadStats.current || 0,
newPrivateMail : StatLog.getUserStatNumByClient(this.client, UserProps.NewPrivateMailCount, MailCountTTLSeconds),
newMessagesAddrTo : StatLog.getUserStatNumByClient(this.client, UserProps.NewAddressedToMessageCount, MailCountTTLSeconds),
}; };
return cb(null); return cb(null);
@ -233,7 +238,7 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
return cb(null); return cb(null);
} }
const nodeStatusItems = getActiveConnectionList({authUsersOnly: false, visibleOnly: false}) const nodeStatusItems = getActiveConnectionList(AllConnections)
.slice(0, nodeStatusView.dimens.height) .slice(0, nodeStatusView.dimens.height)
.map(ac => { .map(ac => {
// Handle pre-authenticated // Handle pre-authenticated

View File

@ -3,7 +3,9 @@
// ENiGMA½ // ENiGMA½
const { MenuModule } = require('./menu_module.js'); const { MenuModule } = require('./menu_module.js');
const { getActiveConnectionList } = require('./client_connections.js'); const {
getActiveConnectionList,
UserVisibleConnections } = require('./client_connections.js');
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
// deps // deps
@ -43,7 +45,7 @@ exports.getModule = class WhosOnlineModule extends MenuModule {
return cb(Errors.MissingMci(`Missing online list MCI ${MciViewIds.onlineList}`)); return cb(Errors.MissingMci(`Missing online list MCI ${MciViewIds.onlineList}`));
} }
const onlineList = getActiveConnectionList().slice(0, onlineListView.height).map( const onlineList = getActiveConnectionList(UserVisibleConnections).slice(0, onlineListView.height).map(
oe => Object.assign(oe, { text : oe.userName, timeOn : _.upperFirst(oe.timeOn.humanize()) }) oe => Object.assign(oe, { text : oe.userName, timeOn : _.upperFirst(oe.timeOn.humanize()) })
); );

View File

@ -105,6 +105,8 @@ There are many predefined MCI codes that can be used anywhere on the system (pla
| `LD` | Date of last caller | | `LD` | Date of last caller |
| `TU` | Total number of users on the system | | `TU` | Total number of users on the system |
| `NT` | Total *new* users *today* | | `NT` | Total *new* users *today* |
| `NM` | Count of new messages **address to the current user** across all message areas in which they have access |
| `NP` | Count of new private mail to the current user |
Some additional special case codes also exist: Some additional special case codes also exist:

View File

@ -78,4 +78,6 @@ The following MCI codes are available:
* `totalMemoryBytes`: Total system memory in bytes. * `totalMemoryBytes`: Total system memory in bytes.
* `freeMemoryBytes`: Free system memory in bytes. * `freeMemoryBytes`: Free system memory in bytes.
* `systemAvgLoad`: System average load. * `systemAvgLoad`: System average load.
* `systemCurrentLoad`: System current load. * `systemCurrentLoad`: System current load.
* `newPrivateMail`: Number of new **privae** mail for current user.
* `newMessagesAddrTo`: Number of new messages **addressed to the current user**.