From 05a4e86cdbd36f1de428d0d811e567a3723cd4df Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 19 Mar 2020 16:11:24 -0400 Subject: [PATCH 1/8] close streams whenever we finish using them. time out if necessary --- lib/storage/README.md | 59 ---------------------- lib/storage/file.js | 112 +++++++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 114 deletions(-) delete mode 100644 lib/storage/README.md diff --git a/lib/storage/README.md b/lib/storage/README.md deleted file mode 100644 index 8fabdf53b..000000000 --- a/lib/storage/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Storage Mechanisms - -Cryptpad's message API is quite simple and modular, and it isn't especially difficult to write alternative modules that employ your favourite datastore. - -There are a few guidelines for creating a module: - -Dependencies for your storage engine **should not** be added to Cryptpad. -Instead, write an adaptor, and place it in `cryptpad/storage/yourAdaptor.js`. - -Alternatively, storage adaptors can be published to npm, and required from your config (once installed). - -## Your adaptor should conform to a simple API. - -It must export an object with a single property, `create`, which is a function. -That function must accept two arguments: - -1. an object containing configuration values - - any configuration values that you require should be well documented - - they should also be named carefully so as to avoid collisions with other modules -2. a callback - - this callback is used to return an object with (currently) two methods - - even if your storage mechanism can be executed synchronously, we use the callback pattern for portability. - -## Methods - -### message(channelName, content, handler) - -When Cryptpad receives a message, it saves it into its datastore using its equivalent of a table for its channel name, and then relays the message to every other client which is participating in the same channel. - -Relaying logic exists outside of the storage module, you simply need to store the message then execute the handler on success. - -### getMessages(channelName, handler, callback) - -When a new client joins, they request the entire history of messages for a particular channel. -This method retreives those messages, and delivers them in order. - -In practice, out of order messages make your clientside application more likely to fail, however, they are generally tolerated. -As a channel accumulates a greater number of messages, the likelihood of the application receiving them in the wrong order becomes greater. -This results in older sessions becoming less reliable. - -This function accepts the name of the channel in which the user is interested, the handler for each message, and the callback to be executed when the last message has been fetched and handled. - -**Note**, the callback is a new addition to this API. -It is only implemented within the leveldb adaptor, making our latest code incompatible with the other back ends. -While we migrate to our new Netflux API, only the leveldb adaptor will be supported. - -## removeChannel(channelName, callback) - -This method is called (optionally, see config.example.js for more info) some amount of time after the last client in a channel disconnects. - -It should remove any history of that channel, and execute a callback which takes an error message as an argument. - -## Documenting your adaptor - -Naturally, you should comment your code well before making a PR. -Failing that, you should definitely add notes to `cryptpad/config.example.js` such that people who wish to install your adaptor know how to do so. - -Notes on how to install the back end, as well as how to install the client for connecting to the back end (as is the case with many datastores), as well as how to configure cryptpad to use your adaptor. -The current configuration file should serve as an example of what to add, and how to comment. diff --git a/lib/storage/file.js b/lib/storage/file.js index bc0830d4e..f8dc3b990 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -58,6 +58,23 @@ var channelExists = function (filepath, cb) { }); }; +const destroyStream = function (stream) { + stream.close(); + setTimeout(function () { + try { stream.destroy(); } catch (err) { console.log(err); } + }, 5000); +}; + +const ensureStreamCloses = function (stream, id, ms) { + return Util.bake(Util.mkTimeout(Util.once(function (err) { + destroyStream(stream, id); + if (err) { + // this can only be a timeout error... + console.log("stream close error:", err, id); + } + }), ms || 15000), []); +}; + // readMessagesBin asynchronously iterates over the messages in a channel log // the handler for each message must call back to read more, which should mean // that this function has a lower memory profile than our classic method @@ -65,9 +82,10 @@ var channelExists = function (filepath, cb) { // it also allows the handler to abort reading at any time const readMessagesBin = (env, id, start, msgHandler, cb) => { const stream = Fs.createReadStream(mkPath(env, id), { start: start }); + const finish = ensureStreamCloses(stream, id); return void readFileBin(stream, msgHandler, function (err) { - try { stream.close(); } catch (err2) { } cb(err); + finish(); }); }; @@ -75,24 +93,13 @@ const readMessagesBin = (env, id, start, msgHandler, cb) => { // returns undefined if the first message was not an object (not an array) var getMetadataAtPath = function (Env, path, _cb) { const stream = Fs.createReadStream(path, { start: 0 }); - - // cb implicitly destroys the stream, if it exists - // and calls back asynchronously no more than once - /* - var cb = Util.once(Util.both(function () { - try { - stream.destroy(); - } catch (err) { - return err; - } - }, Util.mkAsync(_cb))); - */ - - var cb = Util.once(Util.mkAsync(_cb), function () { + const finish = ensureStreamCloses(stream, path); + var cb = Util.once(Util.mkAsync(Util.both(_cb, finish)), function () { throw new Error("Multiple Callbacks"); }); var i = 0; + return readFileBin(stream, function (msgObj, readMore, abort) { const line = msgObj.buff.toString('utf8'); @@ -130,7 +137,8 @@ var closeChannel = function (env, channelName, cb) { if (!env.channels[channelName]) { return void cb(); } try { if (typeof(Util.find(env, [ 'channels', channelName, 'writeStream', 'close'])) === 'function') { - env.channels[channelName].writeStream.close(); + var stream = env.channels[channelName].writeStream; + destroyStream(stream, channelName); } delete env.channels[channelName]; env.openFiles--; @@ -172,11 +180,15 @@ var clearChannel = function (env, channelId, _cb) { */ var readMessages = function (path, msgHandler, _cb) { var stream = Fs.createReadStream(path, { start: 0}); - var cb = Util.once(Util.mkAsync(_cb)); + const finish = ensureStreamCloses(stream, path); + var cb = Util.once(Util.mkAsync(Util.both(finish, _cb))); + return readFileBin(stream, function (msgObj, readMore) { msgHandler(msgObj.buff.toString('utf8')); readMore(); - }, cb); + }, function (err) { + cb(err); + }); }; /* getChannelMetadata @@ -192,9 +204,13 @@ var getChannelMetadata = function (Env, channelId, cb) { }; // low level method for getting just the dedicated metadata channel -var getDedicatedMetadata = function (env, channelId, handler, cb) { +var getDedicatedMetadata = function (env, channelId, handler, _cb) { var metadataPath = mkMetadataPath(env, channelId); var stream = Fs.createReadStream(metadataPath, {start: 0}); + + const finish = ensureStreamCloses(stream, metadataPath); + var cb = Util.both(finish, _cb); + readFileBin(stream, function (msgObj, readMore) { var line = msgObj.buff.toString('utf8'); try { @@ -679,11 +695,12 @@ export type ChainPadServer_ChannelInternal_t = { path: string }; */ -var getChannel = function ( +var getChannel = function ( // XXX BatchRead env, id, _callback /*:(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void*/ ) { + //console.log("getting channel [%s]", id); var callback = Util.once(Util.mkAsync(_callback)); if (env.channels[id]) { var chan = env.channels[id]; @@ -702,6 +719,7 @@ var getChannel = function ( // if you're running out of open files, asynchronously clean up expired files // do it on a shorter timeframe, though (half of normal) setTimeout(function () { + //console.log("FLUSHING UNUSED CHANNELS"); flushUnusedChannels(env, function () { if (env.verbose) { console.log("Approaching open file descriptor limit. Cleaning up"); @@ -740,7 +758,7 @@ var getChannel = function ( fileExists = exists; })); }).nThen(function (waitFor) { - var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); + var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); // XXX env.openFiles++; stream.on('open', waitFor()); stream.on('error', function (err /*:?Error*/) { @@ -762,12 +780,12 @@ var getChannel = function ( // write a message to the disk as raw bytes const messageBin = (env, chanName, msgBin, cb) => { var complete = Util.once(cb); - getChannel(env, chanName, function (err, chan) { + getChannel(env, chanName, function (err, chan) { // XXX if (!chan) { return void complete(err); } chan.onError.push(complete); chan.writeStream.write(msgBin, function () { chan.onError.splice(chan.onError.indexOf(complete), 1); - chan.atime = +new Date(); + chan.atime = +new Date(); // XXX we should just throttle closing, much simpler and less error prone complete(); }); }); @@ -781,33 +799,22 @@ var message = function (env, chanName, msg, cb) { // stream messages from a channel log // TODO replace getMessages with readFileBin var getMessages = function (env, chanName, handler, cb) { - getChannel(env, chanName, function (err, chan) { - if (!chan) { - cb(err); - return; + var errorState = false; + var path = mkPath(env, chanName); + readMessages(path, function (msg) { + if (!msg || errorState) { return; } + try { + handler(msg); + } catch (e) { + errorState = true; + return void cb(e); } - var errorState = false; - readMessages(chan.path, function (msg) { - if (!msg || errorState) { return; } - //console.log(msg); - try { - handler(msg); - } catch (e) { - errorState = true; - return void cb(err); - } - }, function (err) { - if (err) { - errorState = true; - return void cb(err); - } - // is it really, though? what if we hit the limit of open channels - // and 'clean up' in the middle of reading a massive file? - // certainly unlikely - if (!chan) { throw new Error("impossible, flow checking"); } - chan.atime = +new Date(); - cb(); - }); + }, function (err) { + if (err) { + errorState = true; + return void cb(err); + } + cb(); }); }; @@ -832,12 +839,7 @@ var trimChannel = function (env, channelName, hash, _cb) { var ABORT; var cleanUp = function (cb) { - if (tempStream && !tempStream.closed) { - try { - tempStream.close(); - } catch (err) { } - } - + destroyStream(tempStream); Fse.unlink(tempChannelPath, function (err) { // proceed if deleted or if there was nothing to delete if (!err || err.code === 'ENOENT') { return cb(); } From 32cd0f3c4de52f1ccf57ac8c8f9286140fb15c0a Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 19 Mar 2020 16:13:24 -0400 Subject: [PATCH 2/8] increase timeout value from 15s to 45. ought to be enough for anybody --- lib/storage/file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/storage/file.js b/lib/storage/file.js index f8dc3b990..4f7cf54a6 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -72,7 +72,7 @@ const ensureStreamCloses = function (stream, id, ms) { // this can only be a timeout error... console.log("stream close error:", err, id); } - }), ms || 15000), []); + }), ms || 45000), []); }; // readMessagesBin asynchronously iterates over the messages in a channel log From d386e223e4babf1909c728944b94ca05af117949 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 19 Mar 2020 17:33:22 -0400 Subject: [PATCH 3/8] simplify open/close of writeStreams --- lib/storage/file.js | 139 ++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 90 deletions(-) diff --git a/lib/storage/file.js b/lib/storage/file.js index 4f7cf54a6..c868d46a6 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -11,6 +11,7 @@ var Meta = require("../metadata"); var Extras = require("../hk-util"); const readFileBin = require("../stream-file").readFileBin; +const BatchRead = require("../batch-read"); const Schedule = require("../schedule"); const isValidChannelId = function (id) { @@ -59,9 +60,9 @@ var channelExists = function (filepath, cb) { }; const destroyStream = function (stream) { - stream.close(); + try { stream.close(); } catch (err) { console.error(err); } setTimeout(function () { - try { stream.destroy(); } catch (err) { console.log(err); } + try { stream.destroy(); } catch (err) { console.error(err); } }, 5000); }; @@ -141,7 +142,6 @@ var closeChannel = function (env, channelName, cb) { destroyStream(stream, channelName); } delete env.channels[channelName]; - env.openFiles--; cb(); } catch (err) { cb(err); @@ -290,7 +290,7 @@ var writeMetadata = function (env, channelId, data, cb) { // check if a file exists at $path -var checkPath = function (path, callback) { +var checkPath = function (path, callback) { // callback's second arg is never used... Fs.stat(path, function (err) { if (!err) { callback(undefined, true); @@ -632,7 +632,7 @@ var unarchiveChannel = function (env, channelName, cb) { })); }); }; - +/* var flushUnusedChannels = function (env, cb, frame) { var currentTime = +new Date(); @@ -654,6 +654,7 @@ var flushUnusedChannels = function (env, cb, frame) { }); cb(); }; +*/ /* channelBytes calls back with an error or the size (in bytes) of a channel and its metadata @@ -686,106 +687,63 @@ var channelBytes = function (env, chanName, cb) { }); }; -/*:: -export type ChainPadServer_ChannelInternal_t = { - atime: number, - writeStream: typeof(process.stdout), - whenLoaded: ?Array<(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void>, - onError: Array<(?Error)=>void>, - path: string -}; -*/ -var getChannel = function ( // XXX BatchRead - env, - id, - _callback /*:(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void*/ -) { - //console.log("getting channel [%s]", id); - var callback = Util.once(Util.mkAsync(_callback)); +var getChannel = function (env, id, _callback) { + var cb = Util.once(Util.mkAsync(_callback)); + + // if the channel is in memory if (env.channels[id]) { var chan = env.channels[id]; - chan.atime = +new Date(); - if (chan.whenLoaded) { - chan.whenLoaded.push(callback); - } else { - callback(undefined, chan); - } - return; + // delay its pending close a little longer + chan.delayClose(); + // and return its writeStream + return void cb(void 0, chan); } - if (env.openFiles >= env.openFileLimit) { - // FIXME warn if this is the case? - // alternatively use graceful-fs to handle lots of concurrent reads - // if you're running out of open files, asynchronously clean up expired files - // do it on a shorter timeframe, though (half of normal) - setTimeout(function () { - //console.log("FLUSHING UNUSED CHANNELS"); - flushUnusedChannels(env, function () { - if (env.verbose) { - console.log("Approaching open file descriptor limit. Cleaning up"); + // otherwise you need to open it or wait until its pending open completes + return void env.batchGetChannel(id, cb, function (done) { + var path = mkPath(env, id); + var channel = { + onError: [], + }; + nThen(function (w) { + // create the path to the file if it doesn't exist + checkPath(path, w(function (err) { + if (err) { + w.abort(); + return void done(err); } - }, env.channelExpirationMs / 2); - }); - } - var path = mkPath(env, id); - var channel /*:ChainPadServer_ChannelInternal_t*/ = env.channels[id] = { - atime: +new Date(), - writeStream: (undefined /*:any*/), - whenLoaded: [ callback ], - onError: [ ], - path: path - }; - var complete = function (err) { - var whenLoaded = channel.whenLoaded; - // no guarantee stream.on('error') will not cause this to be called multiple times - if (!whenLoaded) { return; } - channel.whenLoaded = undefined; - if (err) { - delete env.channels[id]; - } - if (!channel.writeStream) { - throw new Error("getChannel() complete called without channel writeStream"); // XXX - } - whenLoaded.forEach(function (wl) { wl(err, (err) ? undefined : channel); }); - }; - var fileExists; - nThen(function (waitFor) { - checkPath(path, waitFor(function (err, exists) { - if (err) { - waitFor.abort(); - return void complete(err); - } - fileExists = exists; - })); - }).nThen(function (waitFor) { - var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); // XXX - env.openFiles++; - stream.on('open', waitFor()); - stream.on('error', function (err /*:?Error*/) { - env.openFiles--; - // this might be called after this nThen block closes. - if (channel.whenLoaded) { - complete(err); - } else { + })); + }).nThen(function (w) { + var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); + stream.on('open', w()); + stream.on('error', function (err) { + w.abort(); + // this might be called after this nThen block closes. channel.onError.forEach(function (handler) { handler(err); }); - } + }); + }).nThen(function () { + channel.delayClose = Util.throttle(function () { + delete env.channels[id]; + destroyStream(channel.writeStream, path); + //console.log("closing writestream"); + }, 30000); + channel.delayClose(); + env.channels[id] = channel; + done(void 0, channel); }); - }).nThen(function () { - complete(); }); }; // write a message to the disk as raw bytes const messageBin = (env, chanName, msgBin, cb) => { var complete = Util.once(cb); - getChannel(env, chanName, function (err, chan) { // XXX + getChannel(env, chanName, function (err, chan) { if (!chan) { return void complete(err); } chan.onError.push(complete); chan.writeStream.write(msgBin, function () { chan.onError.splice(chan.onError.indexOf(complete), 1); - chan.atime = +new Date(); // XXX we should just throttle closing, much simpler and less error prone complete(); }); }); @@ -999,8 +957,7 @@ module.exports.create = function (conf, cb) { channels: { }, channelExpirationMs: conf.channelExpirationMs || 30000, verbose: conf.verbose, - openFiles: 0, - openFileLimit: conf.openFileLimit || 2048, + batchGetChannel: BatchRead('store_batch_channel'), }; var it; @@ -1232,7 +1189,8 @@ module.exports.create = function (conf, cb) { }, // iterate over open channels and close any that are not active flushUnusedChannels: function (cb) { - flushUnusedChannels(env, cb); + cb("DEPRECATED"); + //flushUnusedChannels(env, cb); }, // write to a log file log: function (channelName, content, cb) { @@ -1247,7 +1205,8 @@ module.exports.create = function (conf, cb) { } }); }); + /* it = setInterval(function () { flushUnusedChannels(env, function () { }); - }, 5000); + }, 5000);*/ }; From faa133aab8cbbcf681957c0176f7d6e1ea65907a Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 20 Mar 2020 10:22:48 -0400 Subject: [PATCH 4/8] remove an XXX --- lib/check-signature.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/check-signature.js b/lib/check-signature.js index 26839ccee..34ad238e0 100644 --- a/lib/check-signature.js +++ b/lib/check-signature.js @@ -2,8 +2,6 @@ /* global process */ const Nacl = require('tweetnacl/nacl-fast'); -// XXX npm "os" and "child_process" - // TODO if this process is using too much CPU, we can use "cluster" to add load balancing to this code //console.log('New child process', process.pid); process.on('message', function (data) { From dc840c2a704433477923e2c269cf6db3efd1e9cc Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 20 Mar 2020 13:45:45 +0100 Subject: [PATCH 5/8] Fix checkboxes in the markdown renderer #511 --- www/common/diffMarked.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 783aa78a9..73c801ab4 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -117,29 +117,30 @@ define([ // Tasks list var checkedTaskItemPtn = /^\s*(

)?\[[xX]\](<\/p>)?\s*/; var uncheckedTaskItemPtn = /^\s*(

)?\[ ?\](<\/p>)?\s*/; - var bogusCheckPtn = //; + var bogusCheckPtn = //; + var bogusUncheckPtn = //; renderer.listitem = function (text) { var isCheckedTaskItem = checkedTaskItemPtn.test(text); var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text); - var hasBogusInput = bogusCheckPtn.test(text); + var hasBogusCheckedInput = bogusCheckPtn.test(text); + var hasBogusUncheckedInput = bogusUncheckPtn.test(text); + var isCheckbox = true; if (isCheckedTaskItem) { text = text.replace(checkedTaskItemPtn, '') + '\n'; - } - if (isUncheckedTaskItem) { + } else if (isUncheckedTaskItem) { text = text.replace(uncheckedTaskItemPtn, '') + '\n'; - } - if (!isCheckedTaskItem && !isUncheckedTaskItem && hasBogusInput) { - if (/checked/.test(text)) { - text = text.replace(bogusCheckPtn, + } else if (hasBogusCheckedInput) { + text = text.replace(bogusCheckPtn, '') + '\n'; - } else if (/disabled/.test(text)) { - text = text.replace(bogusCheckPtn, + } else if (hasBogusUncheckedInput) { + text = text.replace(bogusUncheckPtn, '') + '\n'; - } + } else { + isCheckbox = false; } - var cls = (isCheckedTaskItem || isUncheckedTaskItem || hasBogusInput) ? ' class="todo-list-item"' : ''; + var cls = (isCheckbox) ? ' class="todo-list-item"' : ''; return '' + text + '\n'; }; restrictedRenderer.listitem = function (text) { From 31c7cecaf9c4c3b34a1f4bbfc559a17db8151be6 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 20 Mar 2020 10:54:01 -0400 Subject: [PATCH 6/8] add a few checks for safety --- lib/storage/file.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/storage/file.js b/lib/storage/file.js index 4f7cf54a6..65ff86bac 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -59,7 +59,8 @@ var channelExists = function (filepath, cb) { }; const destroyStream = function (stream) { - stream.close(); + if (!stream) { return; } + try { stream.close(); } catch (err) { console.error(err); } setTimeout(function () { try { stream.destroy(); } catch (err) { console.log(err); } }, 5000); From 605aae222cc3978d1e75ae12772444bf38fd14e8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 20 Mar 2020 13:21:50 -0400 Subject: [PATCH 7/8] update less devDependency to match client version --- package-lock.json | 425 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 414 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd80fb03d..e9abb0519 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,19 @@ "negotiator": "0.6.2" } }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -55,17 +68,74 @@ "dev": true, "optional": true }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true, + "optional": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + } + } + }, "body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -98,6 +168,13 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, "chainpad-crypto": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.4.tgz", @@ -159,6 +236,16 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -206,6 +293,16 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -220,6 +317,13 @@ "ms": "2.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "optional": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -297,6 +401,17 @@ "is-obj": "^1.0.0" } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -382,6 +497,34 @@ "vary": "~1.1.2" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "optional": true + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true, + "optional": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "optional": true + }, "finalhandler": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", @@ -402,6 +545,25 @@ "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "optional": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -442,6 +604,16 @@ "tiny-each-async": "2.0.3" } }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -475,6 +647,24 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "optional": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -514,6 +704,18 @@ "statuses": ">= 1.4.0 < 2" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -573,18 +775,39 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, "js-base64": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "dev": true }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, "jshint": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.11.0.tgz", @@ -601,6 +824,27 @@ "strip-json-comments": "1.0.x" } }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -609,6 +853,19 @@ "graceful-fs": "^4.1.6" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "jszip": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", @@ -654,18 +911,28 @@ } }, "less": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.1.tgz", - "integrity": "sha1-bL/qIrO4MDBOml+zcdVPpIDJ188=", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.7.1.tgz", + "integrity": "sha512-Cmf5XJlzNklkBC8eAa+Ef16AHUBAkApHNAw3x9Vmn84h2BvGrri5Id7kf6H1n6SN74Fc0WdHIRPlFMxsl0eJkA==", "dev": true, "requires": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", "image-size": "~0.5.0", - "mime": "^1.2.11", + "mime": "^1.4.1", "mkdirp": "^0.5.0", "promise": "^7.1.1", - "source-map": "^0.5.3" + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } } }, "lesshint": { @@ -781,20 +1048,20 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true, "optional": true }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "optional": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } }, "ms": { @@ -817,6 +1084,13 @@ "resolved": "https://registry.npmjs.org/nthen/-/nthen-0.1.8.tgz", "integrity": "sha512-Oh2CwIbhj+wUT94lQV7LKmmgw3UYAGGd8oLIqp6btQN3Bz3PuWp4BuvtUo35H3rqDknjPfKx5P6mt7v+aJNjcw==" }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "optional": true + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -871,6 +1145,13 @@ "pify": "^3.0.0" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true, + "optional": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -1015,11 +1296,25 @@ "dev": true, "optional": true }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true, + "optional": true + }, "pull-stream": { "version": "3.6.14", "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.6.14.tgz", "integrity": "sha512-KIqdvpqHHaTUA2mCYcLG1ibEbu/LCKoJZsBWyv9lSYtPkJPBq8m3Hxa103xHi6D2thj5YXa0TqK3L3GUkwgnew==" }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "optional": true + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -1062,6 +1357,35 @@ "string_decoder": "~0.10.x" } }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -1172,6 +1496,33 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1230,6 +1581,27 @@ "os-tmpdir": "~1.0.1" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "optional": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "tweetnacl": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.12.2.tgz", @@ -1270,6 +1642,16 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "optional": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1281,11 +1663,30 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true, + "optional": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index a1817986d..f042d7981 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "jshint": "^2.10.2", - "less": "2.7.1", + "less": "3.7.1", "lesshint": "^4.5.0", "selenium-webdriver": "^3.6.0" }, From bf2e5aeca109edfb4428aae334e4ce64f21caad4 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 20 Mar 2020 17:04:18 -0400 Subject: [PATCH 8/8] add an admin panel block to display the open file count --- www/admin/inner.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/www/admin/inner.js b/www/admin/inner.js index ad7083c6c..acc789784 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -43,6 +43,7 @@ define([ 'stats': [ 'cp-admin-active-sessions', 'cp-admin-active-pads', + 'cp-admin-open-files', 'cp-admin-registered', 'cp-admin-disk-usage', ], @@ -119,6 +120,17 @@ define([ }); return $div; }; + create['open-files'] = function () { + var key = 'open-files'; // XXX + var $div = makeBlock(key); + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'GET_FILE_DESCRIPTOR_COUNT', + }, function (e, data) { + console.log(e, data); + $div.append(h('pre', String(data))); + }); + return $div; + }; create['registered'] = function () { var key = 'registered'; var $div = makeBlock(key);