Merge from master + add MCI codes
* FT, DD, FB, DB MCI codes and backing system stats
This commit is contained in:
commit
3a7f7750ab
|
@ -9,6 +9,7 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
|
|||
* New `PV` ACS check for arbitrary user properties. See [ACS](./docs/configuration/acs.md) for details.
|
||||
* The `message` arg used by `msg_list` has been deprecated. Please starting using `messageIndex` for this purpose. Support for `message` will be removed in the future.
|
||||
* A number of new MCI codes (see [MCI](./docs/art/mci.md))
|
||||
* Added ability to export/download messages. This is enabled in the default menu. See `messageAreaViewPost` in [the default message base template](./misc/menu_templates/message_base.in.hjson) and look for the download options (`@method:addToDownloadQueue`, etc.) for details on adding to your system!
|
||||
|
||||
## 0.0.11-beta
|
||||
* Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point!
|
||||
|
|
Binary file not shown.
39
core/bbs.js
39
core/bbs.js
|
@ -13,6 +13,7 @@ const resolvePath = require('./misc_util.js').resolvePath;
|
|||
const UserProps = require('./user_property.js');
|
||||
const SysProps = require('./system_property.js');
|
||||
const SysLogKeys = require('./system_log.js');
|
||||
const UserLogNames = require('./user_log_name');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -209,7 +210,7 @@ function initialize(cb) {
|
|||
|
||||
process.on('SIGINT', shutdownSystem);
|
||||
|
||||
require('later').date.localTime(); // use local times for later.js/scheduling
|
||||
require('@breejs/later').date.localTime(); // use local times for later.js/scheduling
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
|
@ -287,6 +288,42 @@ function initialize(cb) {
|
|||
return callback(null);
|
||||
});
|
||||
},
|
||||
function initUserTransferStats(callback) {
|
||||
const StatLog = require('./stat_log');
|
||||
|
||||
const entries = [
|
||||
[ UserLogNames.UlFiles, [ SysProps.FileUlTodayCount, 'count' ] ],
|
||||
[ UserLogNames.UlFileBytes, [ SysProps.FileUlTodayBytes, 'obj' ] ],
|
||||
[ UserLogNames.DlFiles, [ SysProps.FileDlTodayCount, 'count' ] ],
|
||||
[ UserLogNames.DlFileBytes, [ SysProps.FileDlTodayBytes, 'obj' ] ],
|
||||
];
|
||||
|
||||
async.each(entries, (entry, nextEntry) => {
|
||||
const [ logName, [sysPropName, resultType] ] = entry;
|
||||
|
||||
const filter = {
|
||||
logName,
|
||||
resultType,
|
||||
date : moment(),
|
||||
};
|
||||
|
||||
filter.logName = logName;
|
||||
|
||||
StatLog.findUserLogEntries(filter, (err, stat) => {
|
||||
if (!err) {
|
||||
if (resultType === 'obj') {
|
||||
stat = stat.reduce( (bytes, entry) => bytes + parseInt(entry.log_value) || 0, 0);
|
||||
}
|
||||
|
||||
StatLog.setNonPersistentSystemStat(sysPropName, stat);
|
||||
}
|
||||
return nextEntry(null);
|
||||
});
|
||||
},
|
||||
() => {
|
||||
return callback(null);
|
||||
});
|
||||
},
|
||||
function initMessageStats(callback) {
|
||||
return require('./message_area.js').startup(callback);
|
||||
},
|
||||
|
|
|
@ -132,6 +132,9 @@ ClientTerminal.prototype.isANSI = function() {
|
|||
//
|
||||
// Reports from various terminals
|
||||
//
|
||||
// NetRunner v2.00beta 20
|
||||
// * This version adds 256 colors and reports as "ansi-256color"
|
||||
//
|
||||
// syncterm:
|
||||
// * SyncTERM
|
||||
//
|
||||
|
@ -150,7 +153,7 @@ ClientTerminal.prototype.isANSI = function() {
|
|||
// linux:
|
||||
// * JuiceSSH (note: TERM=linux also)
|
||||
//
|
||||
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].includes(this.termType);
|
||||
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color' ].includes(this.termType);
|
||||
};
|
||||
|
||||
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)
|
||||
|
|
|
@ -8,7 +8,7 @@ const Log = require('./logger.js').log;
|
|||
const { Errors } = require('./enig_error.js');
|
||||
|
||||
const _ = require('lodash');
|
||||
const later = require('later');
|
||||
const later = require('@breejs/later');
|
||||
const path = require('path');
|
||||
const pty = require('node-pty');
|
||||
const sane = require('sane');
|
||||
|
|
|
@ -478,6 +478,9 @@ class FileAreaWebAccess {
|
|||
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, 1);
|
||||
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, dlBytes);
|
||||
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayCount, 1);
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayBytes, dlBytes);
|
||||
|
||||
return callback(null, user);
|
||||
},
|
||||
function sendEvent(user, callback) {
|
||||
|
|
|
@ -544,6 +544,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount);
|
||||
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes);
|
||||
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayCount, downloadCount);
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayBytes, downloadBytes);
|
||||
|
||||
fileIds.forEach(fileId => {
|
||||
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
||||
});
|
||||
|
@ -575,6 +578,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
StatLog.incrementSystemStat(SysProps.FileUlTotalCount, uploadCount);
|
||||
StatLog.incrementSystemStat(SysProps.FileUlTotalBytes, uploadBytes);
|
||||
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayCount, uploadCount);
|
||||
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayBytes, uploadBytes);
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
|
113
core/fse.js
113
core/fse.js
|
@ -21,17 +21,25 @@ const {
|
|||
isAnsi, stripAnsiControlCodes,
|
||||
insert
|
||||
} = require('./string_util.js');
|
||||
const { stripMciColorCodes } = require('./color_codes.js');
|
||||
const Config = require('./config.js').get;
|
||||
const { getAddressedToInfo } = require('./mail_util.js');
|
||||
const Events = require('./events.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const SysProps = require('./system_property.js');
|
||||
const FileArea = require('./file_base_area.js');
|
||||
const FileEntry = require('./file_entry.js');
|
||||
const DownloadQueue = require('./download_queue.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const assert = require('assert');
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
const fse = require('fs-extra');
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const sanatizeFilename = require('sanitize-filename');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Full Screen Editor (FSE)',
|
||||
|
@ -255,7 +263,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
viewModeMenuHelp : function(formData, extraArgs, cb) {
|
||||
self.viewControllers.footerView.setFocus(false);
|
||||
return self.displayHelp(cb);
|
||||
}
|
||||
},
|
||||
|
||||
addToDownloadQueue : (formData, extraArgs, cb) => {
|
||||
this.viewControllers.footerView.setFocus(false);
|
||||
return this.addToDownloadQueue(cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -853,7 +866,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
this.setHeaderText(MciViewIds.header.from, this.message.fromUserName);
|
||||
this.setHeaderText(MciViewIds.header.to, this.message.toUserName);
|
||||
this.setHeaderText(MciViewIds.header.subject, this.message.subject);
|
||||
this.setHeaderText(MciViewIds.header.modTimestamp, moment(this.message.modTimestamp).format(this.client.currentTheme.helpers.getDateTimeFormat()));
|
||||
|
||||
this.setHeaderText(MciViewIds.header.modTimestamp, moment(this.message.modTimestamp).format(
|
||||
this.menuConfig.config.modTimestampFormat || this.client.currentTheme.helpers.getDateTimeFormat())
|
||||
);
|
||||
|
||||
this.setHeaderText(MciViewIds.header.msgNum, (this.messageIndex + 1).toString());
|
||||
this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString());
|
||||
|
||||
|
@ -901,6 +918,98 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
);
|
||||
}
|
||||
|
||||
addToDownloadQueue(cb) {
|
||||
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
||||
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
|
||||
|
||||
const msgInfo = this.getHeaderFormatObj();
|
||||
|
||||
const outputFileName = paths.join(
|
||||
sysTempDownloadDir,
|
||||
sanatizeFilename(
|
||||
`(${msgInfo.messageId}) ${msgInfo.subject}_(${this.message.modTimestamp.format('YYYY-MM-DD')}).txt`)
|
||||
);
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
const header =
|
||||
`+${'-'.repeat(79)}
|
||||
| To : ${msgInfo.toUserName}
|
||||
| From : ${msgInfo.fromUserName}
|
||||
| When : ${moment(this.message.modTimestamp).format('dddd, MMMM Do YYYY, h:mm:ss a (UTCZ)')}
|
||||
| Subject : ${msgInfo.subject}
|
||||
| ID : ${this.message.messageUuid} (${msgInfo.messageId})
|
||||
+${'-'.repeat(79)}
|
||||
`;
|
||||
const body = this.viewControllers.body
|
||||
.getView(MciViewIds.body.message)
|
||||
.getData( { forceLineTerms : true } );
|
||||
|
||||
const cleanBody = stripMciColorCodes(
|
||||
stripAnsiControlCodes(body, { all : true } )
|
||||
);
|
||||
|
||||
const exportedMessage = `${header}\r\n${cleanBody}`;
|
||||
|
||||
fse.mkdirs(sysTempDownloadDir, err => {
|
||||
return callback(err, exportedMessage);
|
||||
});
|
||||
},
|
||||
(exportedMessage, callback) => {
|
||||
return fs.writeFile(outputFileName, exportedMessage, 'utf8', callback);
|
||||
},
|
||||
(callback) => {
|
||||
fs.stat(outputFileName, (err, stats) => {
|
||||
return callback(err, stats.size);
|
||||
});
|
||||
},
|
||||
(fileSize, callback) => {
|
||||
const newEntry = new FileEntry({
|
||||
areaTag : sysTempDownloadArea.areaTag,
|
||||
fileName : paths.basename(outputFileName),
|
||||
storageTag : sysTempDownloadArea.storageTags[0],
|
||||
meta : {
|
||||
upload_by_username : this.client.user.username,
|
||||
upload_by_user_id : this.client.user.userId,
|
||||
byte_size : fileSize,
|
||||
session_temp_dl : 1, // download is valid until session is over
|
||||
}
|
||||
});
|
||||
|
||||
newEntry.desc = `${msgInfo.messageId} - ${msgInfo.subject}`;
|
||||
|
||||
newEntry.persist(err => {
|
||||
if(!err) {
|
||||
// queue it!
|
||||
DownloadQueue.get(this.client).addTemporaryDownload(newEntry);
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
(callback) => {
|
||||
const artSpec = this.menuConfig.config.art.expToDlQueue ||
|
||||
Buffer.from('Exported message has been added to your download queue!');
|
||||
this.displayAsset(
|
||||
artSpec,
|
||||
{ clearScreen : true },
|
||||
() => {
|
||||
this.client.waitForKeyPress( () => {
|
||||
this.redrawScreen( () => {
|
||||
this.viewControllers[this.getFooterName()].setFocus(true);
|
||||
return callback(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayQuoteBuilder() {
|
||||
//
|
||||
// Clear body area
|
||||
|
|
|
@ -634,7 +634,9 @@ module.exports = class Message {
|
|||
self.fromUserName = msgRow.from_user_name;
|
||||
self.subject = msgRow.subject;
|
||||
self.message = msgRow.message;
|
||||
self.modTimestamp = moment(msgRow.modified_timestamp);
|
||||
|
||||
// We use parseZone() to *preserve* the time zone information
|
||||
self.modTimestamp = moment.parseZone(msgRow.modified_timestamp);
|
||||
|
||||
return callback(err);
|
||||
}
|
||||
|
|
|
@ -102,11 +102,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
|||
|
||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||
return cb(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadMessageByUuid(uuid, cb) {
|
||||
const msg = new Message();
|
||||
msg.load( { uuid : uuid, user : this.client.user }, () => {
|
||||
|
|
|
@ -292,10 +292,22 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
TP : function totalMessagesOnSystem() { // Obv/2
|
||||
return StatLog.getFriendlySystemStat(SysProps.MessageTotalCount, 0);
|
||||
},
|
||||
FT : function totalUploadsToday() { // Obv/2
|
||||
return StatLog.getFriendlySystemStat(SysProps.FileUlTodayCount, 0);
|
||||
},
|
||||
FB : function totalUploadBytesToday() {
|
||||
const byteSize = StatLog.getSystemStatNum(SysProps.FileUlTodayBytes);
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
DD : function totalDownloadsToday() { // iNiQUiTY
|
||||
return StatLog.getFriendlySystemStat(SysProps.FileDlTodayCount, 0);
|
||||
},
|
||||
DB : function totalDownloadBytesToday() {
|
||||
const byteSize = StatLog.getSystemStatNum(SysProps.FileDlTodayBytes);
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
|
||||
// :TODO: NT - New users today (Obv/2)
|
||||
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
||||
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
||||
// :TODO: LC - name of last caller to system (Obv/2)
|
||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||
// :TODO: ?? - Total users on system
|
||||
|
|
|
@ -30,7 +30,7 @@ const _ = require('lodash');
|
|||
const paths = require('path');
|
||||
const async = require('async');
|
||||
const fs = require('graceful-fs');
|
||||
const later = require('later');
|
||||
const later = require('@breejs/later');
|
||||
const temptmp = require('temptmp').createTrackedSession('ftn_bso');
|
||||
const assert = require('assert');
|
||||
const sane = require('sane');
|
||||
|
|
164
core/stat_log.js
164
core/stat_log.js
|
@ -259,75 +259,18 @@ class StatLog {
|
|||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Find System Log entries by |filter|:
|
||||
|
||||
filter.logName (required)
|
||||
filter.resultType = (obj) | count
|
||||
where obj contains timestamp and log_value
|
||||
filter.limit
|
||||
filter.date - exact date to filter against
|
||||
filter.order = (timestamp) | timestamp_asc | timestamp_desc | random
|
||||
*/
|
||||
//
|
||||
// Find System Log entry(s) by |filter|:
|
||||
//
|
||||
// - logName: Name of log (required)
|
||||
// - resultType: 'obj' | 'count' (default='obj')
|
||||
// - limit: Limit returned results
|
||||
// - date: exact date to filter against
|
||||
// - order: 'timestamp' | 'timestamp_asc' | 'timestamp_desc' | 'random'
|
||||
// (default='timestamp')
|
||||
//
|
||||
findSystemLogEntries(filter, cb) {
|
||||
filter = filter || {};
|
||||
if(!_.isString(filter.logName)) {
|
||||
return cb(Errors.MissingParam('filter.logName is required'));
|
||||
}
|
||||
|
||||
filter.resultType = filter.resultType || 'obj';
|
||||
filter.order = filter.order || 'timestamp';
|
||||
|
||||
let sql;
|
||||
if('count' === filter.resultType) {
|
||||
sql =
|
||||
`SELECT COUNT() AS count
|
||||
FROM system_event_log`;
|
||||
} else {
|
||||
sql =
|
||||
`SELECT timestamp, log_value
|
||||
FROM system_event_log`;
|
||||
}
|
||||
|
||||
sql += ' WHERE log_name = ?';
|
||||
|
||||
if(filter.date) {
|
||||
filter.date = moment(filter.date);
|
||||
sql += ` AND DATE(timestamp, "localtime") = DATE("${filter.date.format('YYYY-MM-DD')}")`;
|
||||
}
|
||||
|
||||
if('count' !== filter.resultType) {
|
||||
switch(filter.order) {
|
||||
case 'timestamp' :
|
||||
case 'timestamp_asc' :
|
||||
sql += ' ORDER BY timestamp ASC';
|
||||
break;
|
||||
|
||||
case 'timestamp_desc' :
|
||||
sql += ' ORDER BY timestamp DESC';
|
||||
break;
|
||||
|
||||
case 'random' :
|
||||
sql += ' ORDER BY RANDOM()';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(_.isNumber(filter.limit) && 0 !== filter.limit) {
|
||||
sql += ` LIMIT ${filter.limit}`;
|
||||
}
|
||||
|
||||
sql += ';';
|
||||
|
||||
if('count' === filter.resultType) {
|
||||
sysDb.get(sql, [ filter.logName ], (err, row) => {
|
||||
return cb(err, row ? row.count : 0);
|
||||
});
|
||||
} else {
|
||||
sysDb.all(sql, [ filter.logName ], (err, rows) => {
|
||||
return cb(err, rows);
|
||||
});
|
||||
}
|
||||
return this._findLogEntries('system_event_log', filter, cb);
|
||||
}
|
||||
|
||||
getSystemLogEntries(logName, order, limit, cb) {
|
||||
|
@ -389,6 +332,22 @@ class StatLog {
|
|||
return cb(null);
|
||||
}
|
||||
|
||||
//
|
||||
// Find User Log entry(s) by |filter|:
|
||||
//
|
||||
// - logName: Name of log (required)
|
||||
// - userId: User ID in which to restrict entries to (missing=all)
|
||||
// - sessionId: Session ID in which to restrict entries to (missing=any)
|
||||
// - resultType: 'obj' | 'count' (default='obj')
|
||||
// - limit: Limit returned results
|
||||
// - date: exact date to filter against
|
||||
// - order: 'timestamp' | 'timestamp_asc' | 'timestamp_desc' | 'random'
|
||||
// (default='timestamp')
|
||||
//
|
||||
findUserLogEntries(filter, cb) {
|
||||
return this._findLogEntries('user_event_log', filter, cb);
|
||||
}
|
||||
|
||||
_refreshSystemStat(statName) {
|
||||
switch (statName) {
|
||||
case SysProps.SystemLoadStats :
|
||||
|
@ -431,6 +390,75 @@ class StatLog {
|
|||
// :TODO: log me
|
||||
});
|
||||
}
|
||||
|
||||
_findLogEntries(logTable, filter, cb) {
|
||||
filter = filter || {};
|
||||
if(!_.isString(filter.logName)) {
|
||||
return cb(Errors.MissingParam('filter.logName is required'));
|
||||
}
|
||||
|
||||
filter.resultType = filter.resultType || 'obj';
|
||||
filter.order = filter.order || 'timestamp';
|
||||
|
||||
let sql;
|
||||
if('count' === filter.resultType) {
|
||||
sql =
|
||||
`SELECT COUNT() AS count
|
||||
FROM ${logTable}`;
|
||||
} else {
|
||||
sql =
|
||||
`SELECT timestamp, log_value
|
||||
FROM ${logTable}`;
|
||||
}
|
||||
|
||||
sql += ' WHERE log_name = ?';
|
||||
|
||||
if (_.isNumber(filter.userId)) {
|
||||
sql += ` AND user_id = ${filter.userId}`;
|
||||
}
|
||||
|
||||
if (filter.sessionId) {
|
||||
sql += ` AND session_id = ${filter.sessionId}`;
|
||||
}
|
||||
|
||||
if(filter.date) {
|
||||
filter.date = moment(filter.date);
|
||||
sql += ` AND DATE(timestamp, "localtime") = DATE("${filter.date.format('YYYY-MM-DD')}")`;
|
||||
}
|
||||
|
||||
if('count' !== filter.resultType) {
|
||||
switch(filter.order) {
|
||||
case 'timestamp' :
|
||||
case 'timestamp_asc' :
|
||||
sql += ' ORDER BY timestamp ASC';
|
||||
break;
|
||||
|
||||
case 'timestamp_desc' :
|
||||
sql += ' ORDER BY timestamp DESC';
|
||||
break;
|
||||
|
||||
case 'random' :
|
||||
sql += ' ORDER BY RANDOM()';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(_.isNumber(filter.limit) && 0 !== filter.limit) {
|
||||
sql += ` LIMIT ${filter.limit}`;
|
||||
}
|
||||
|
||||
sql += ';';
|
||||
|
||||
if('count' === filter.resultType) {
|
||||
sysDb.get(sql, [ filter.logName ], (err, row) => {
|
||||
return cb(err, row ? row.count : 0);
|
||||
});
|
||||
} else {
|
||||
sysDb.all(sql, [ filter.logName ], (err, rows) => {
|
||||
return cb(err, rows);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new StatLog();
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
//
|
||||
module.exports = {
|
||||
UserAddedRumorz : 'system_rumorz',
|
||||
UserLoginHistory : 'user_login_history',
|
||||
UserLoginHistory : 'user_login_history', // JSON object
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,11 @@ module.exports = {
|
|||
FileDlTotalCount : 'dl_total_count',
|
||||
FileDlTotalBytes : 'dl_total_bytes',
|
||||
|
||||
FileUlTodayCount : 'ul_today_count', // non-persistent
|
||||
FileUlTodayBytes : 'ul_today_bytes', // non-persistent
|
||||
FileDlTodayCount : 'dl_today_count', // non-persistent
|
||||
FileDlTodayBytes : 'dl_today_bytes', // non-persistent
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -88,9 +88,13 @@ There are many predefined MCI codes that can be used anywhere on the system (pla
|
|||
| `SU` | Total uploads, system wide |
|
||||
| `SP` | Total uploaded amount, system wide (formatted to appropriate bytes/megs/etc.) |
|
||||
| `TF` | Total number of files on the system |
|
||||
| `TB` | Total amount of files on the system (formatted to appropriate bytes/megs/gigs/etc.) |
|
||||
| `TB` | Total file base size (formatted to appropriate bytes/megs/gigs/etc.) |
|
||||
| `TP` | Total messages posted/imported to the system *currently* |
|
||||
| `PT` | Total messages posted/imported to the system *today* |
|
||||
| `FT` | Total number of uploads to the system *today* |
|
||||
| `FB` | Total upload amount *today* (formatted to appropriate bytes/megs/etc. ) |
|
||||
| `DD` | Total number of downloads from the system *today* |
|
||||
| `DB` | Total download amount *today* (formatted to appropriate bytes/megs/etc. ) |
|
||||
| `MB` | System memory |
|
||||
| `MF` | System _free_ memory |
|
||||
| `LA` | System load average (e.g. 0.25)<br>(Not available for all platforms) |
|
||||
|
@ -113,32 +117,53 @@ Some additional special case codes also exist:
|
|||
the time so also check out [core/predefined_mci.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js)
|
||||
for a full listing.
|
||||
|
||||
:note: Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, etc.
|
||||
:memo: Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, etc.
|
||||
|
||||
|
||||
## Views
|
||||
A **View** is a control placed on a **form** that can display variable data or collect input. One example of a View is
|
||||
a Vertical Menu (`%VM`): Old-school BBSers may recognize this as a lightbar menu.
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|----------------------|------------------|
|
||||
| `TL` | Text Label | Displays text |
|
||||
| `ET` | Edit Text | Collect user input |
|
||||
| `ME` | Masked Edit Text | Collect user input using a *mask* |
|
||||
| `MT` | Multi Line Text Edit | Multi line edit control |
|
||||
| `BT` | Button | A button |
|
||||
| `VM` | Vertical Menu | A vertical menu aka a vertical lightbar |
|
||||
| `HM` | Horizontal Menu | A horizontal menu aka a horizontal lightbar |
|
||||
| `SM` | Spinner Menu | A spinner input control |
|
||||
| `TM` | Toggle Menu | A toggle menu commonly used for Yes/No style input |
|
||||
| `KE` | Key Entry | A *single* key input control |
|
||||
| Code | Name | Description | Notes |
|
||||
|------|----------------------|------------------|-------|
|
||||
| `TL` | Text Label | Displays text | Static content |
|
||||
| `ET` | Edit Text | Collect user input | Single line entry |
|
||||
| `ME` | Masked Edit Text | Collect user input using a *mask* | See **Mask Edits** below |
|
||||
| `MT` | Multi Line Text Edit | Multi line edit control | Used for FSE, display of FILE_ID.DIZ, etc. |
|
||||
| `BT` | Button | A button | ...it's a button |
|
||||
| `VM` | Vertical Menu | A vertical menu | AKA a vertical lightbar; Useful for lists |
|
||||
| `HM` | Horizontal Menu | A horizontal menu | AKA a horizontal lightbar |
|
||||
| `SM` | Spinner Menu | A spinner input control | Select *one* from multiple options |
|
||||
| `TM` | Toggle Menu | A toggle menu | Commonly used for Yes/No style input |
|
||||
| `KE` | Key Entry | A *single* key input control | Think hotkeys |
|
||||
|
||||
:information_source: Peek at [/core/mci_view_factory.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js) to
|
||||
see additional information.
|
||||
:information_source: Peek at [/core/mci_view_factory.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js) to see additional information.
|
||||
|
||||
### Mask Edits
|
||||
Mask Edits (`%ME`) use the special `maskPattern` property to control a _mask_. This can be useful for gathering dates, phone numbers, so on.
|
||||
|
||||
`maskPattern`'s can be composed of the following characters:
|
||||
* `#`: Numeric 0-9
|
||||
* `A`: Alpha a-z, A-Z
|
||||
* `@`: Alphanumeric (combination of the previous patterns)
|
||||
* `&`: Any "printable" character
|
||||
|
||||
Any other characters are literals.
|
||||
|
||||
An example of a mask for a date may look like this: `##/##/####`.
|
||||
|
||||
Additionally, the following theme stylers can be applied:
|
||||
* `styleSGR1`: Controls literal character colors for non-focused controls
|
||||
* `styleSGR2`: Controls literal character colors for focused controls
|
||||
* `styleSGR3`: Controls fill colors (characters that have not yet received input).
|
||||
|
||||
All of the style properties can take pipe codes such as `|00|08`.
|
||||
|
||||
### View Identifiers
|
||||
As mentioned above, MCI codes can (and often should) be explicitly tied to a *View Identifier*. Simply speaking this is a number representing the particular view. These can be useful to reference in code, apply themes, etc.
|
||||
|
||||
A view ID is tied to a MCI code by specifying it after the code. For example: `%VM1` or `%SM10`.
|
||||
|
||||
## Properties & Theming
|
||||
Predefined MCI codes and other Views can have properties set via `menu.hjson` and further *themed* via `theme.hjson`. See [Themes](themes.md) for more information on this subject.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
layout: page
|
||||
title: User List
|
||||
title: The Show Art Module
|
||||
---
|
||||
## The Show Art Module
|
||||
The built in `show_art` module add some advanced ways in which you can configure your system to display art assets beyond what a standard menu entry can provide. For example, based on user selection of a file or message base area.
|
||||
|
|
|
@ -481,6 +481,7 @@
|
|||
body: MSGBODY
|
||||
footerView: MSGVFTR
|
||||
help: MSGVHLP
|
||||
expToDlQueue: mb_export_dl_queue
|
||||
},
|
||||
editorMode: view
|
||||
editorType: area
|
||||
|
@ -525,7 +526,7 @@
|
|||
mci: {
|
||||
HM1: {
|
||||
// :TODO: (#)Jump/(L)Index (msg list)/Last
|
||||
items: [ "prev", "next", "reply", "quit", "help" ]
|
||||
items: [ "prev", "next", "reply", "quit", "download", "help" ]
|
||||
focusItemIndex: 1
|
||||
}
|
||||
}
|
||||
|
@ -552,6 +553,10 @@
|
|||
}
|
||||
{
|
||||
value: { 1: 4 }
|
||||
action: @method:addToDownloadQueue
|
||||
}
|
||||
{
|
||||
value: { 1: 5 }
|
||||
action: @method:viewModeMenuHelp
|
||||
}
|
||||
]
|
||||
|
@ -576,6 +581,10 @@
|
|||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "d", "shift + d" ]
|
||||
action: @method:addToDownloadQueue
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:viewModeMenuHelp
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"iconv-lite": "^0.6.2",
|
||||
"ini-config-parser": "^1.0.4",
|
||||
"inquirer": "7.3.3",
|
||||
"later": "1.2.0",
|
||||
"@breejs/later" : "^4.0.2",
|
||||
"lodash": "4.17.20",
|
||||
"lru-cache": "^5.1.1",
|
||||
"mime-types": "2.1.27",
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -23,6 +23,11 @@
|
|||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@breejs/later@^4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@breejs/later/-/later-4.0.2.tgz#38c85cc98b717c7a196f87238090adaea01f8c9e"
|
||||
integrity sha512-EN0SlbyYouBdtZis1htdsgGlwFePzkXPwdIeqaBaavxkJT1G2/bitc2LSixjv45z2njXslxlJI1mW2O/Gmrb+A==
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
|
||||
|
@ -1392,11 +1397,6 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
|||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
|
||||
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
|
||||
|
||||
later@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/later/-/later-1.2.0.tgz#f2cf6c4dd7956dd2f520adf0329836e9876bad0f"
|
||||
integrity sha1-8s9sTdeVbdL1IK3wMpg26YdrrQ8=
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
|
|
Loading…
Reference in New Issue