UNPKG

node-mongo-admin

Version:

A simple web application to visualize mongo data inspired by PHPMyAdmin

353 lines (334 loc) 9.67 kB
/* globals Panel*/ /** * @file Declare some mongo special types, like ObjectId */ 'use strict' let json = {} /** * @param {string} isoStr */ function ISODate(isoStr) { // eslint-disable-line no-unused-vars return new Date(isoStr) } /** * new ObjectId() and ObjectId() both have the same effect * @class * @param {string} id * @property {string} $oid */ function ObjectId(id) { if (!(this instanceof ObjectId)) { return new ObjectId(id) } if (typeof id !== 'string' || !id.match(/^[0-9a-f]{24}$/i)) { throw new Error('Expect id to be a 24-hex-char string') } this.$oid = id } ObjectId.prototype.toString = function () { return this.$oid } /** * @class * @param {number} type * @param {string} binary */ function BinData(type, binary) { if (!(this instanceof BinData)) { return new BinData(type, binary) } this.$binary = binary this.$type = type } /** * @class * @param {string} ref * @param {*} id */ function DBRef(ref, id) { if (!(this instanceof DBRef)) { return new DBRef(ref, id) } this.$ref = ref this.$id = id } /** * @class */ function MinKey() { if (!(this instanceof MinKey)) { return new MinKey() } this.$minKey = 1 } /** * @class */ function MaxKey() { if (!(this instanceof MaxKey)) { return new MaxKey() } this.$maxKey = 1 } /** * @class * @param {string} numberLong */ function Long(numberLong) { if (!(this instanceof Long)) { return new Long(numberLong) } this.$numberLong = numberLong } /** * Reviver function to use with JSON.parse * @param {string} key * @param {*} value * @returns {*} */ json.reviver = function (key, value) { if (value && typeof value === 'object') { if (typeof value.$oid === 'string') { return new ObjectId(value.$oid) } else if (typeof value.$binary === 'string') { return new BinData(value.$type, value.$binary) } else if (typeof value.$date === 'number') { return new Date(value.$date) } else if (typeof value.$regex === 'string') { return new RegExp(value.$regex, value.$options) } else if (typeof value.$ref === 'string') { return new DBRef(value.$ref, json.reviver('', value.$id)) } else if (value.$undefined === true) { return undefined } else if (value.$minKey === 1) { return new MinKey() } else if (value.$maxKey === 1) { return new MaxKey() } else if (typeof value.$numberLong === 'string') { return new Long(value.$numberLong) } else if (typeof value.$infinity === 'number') { return value.$infinity * Infinity } else if (value.$nan === 1) { return NaN } return value } return value } /** * Convert the given value into a string * @param {*} value * @param {boolean} [html] - false to return plain text, true to return html text * @param {boolean} [pretty] - false to return one-line text, true to return multi-line with tabs * @param {boolean} [localDate] - show date in local (browser) time (only works if html=true) * @param {boolean} [hexBinary] - show binary date in hex (only works if html=true) * @param {boolean} [oidTimestamp] - show object-id's timestamp (only works if html=true) * @returns {string} */ json.stringify = function (value, html, pretty, localDate, hexBinary, oidTimestamp) { let finalStr = '', indentLevel = 0 let hasBreak = true let getNL = function () { if (!pretty) { return '' } let i, nl = '\n' for (i = 0; i < indentLevel; i++) { nl += ' ' } return nl } let escapeKey = function (key) { if (key.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/)) { return key } key = '\'' + key.replace(/'/g, '\\\'') + '\'' return html ? Panel.escape(key) : key } let formatDate = function (date) { return localDate ? date.toLocaleString() : date.toISOString() } let formatObjectId = function (oid) { if (!oidTimestamp) { return oid.$oid } // Convert the first 4 bytes to Unix Timestamp let time = parseInt(oid.$oid.substr(0, 8), 16), date = new Date(time * 1000) return date.toLocaleString() } let formatBinary = function (base64) { let hex = '', codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', i, c1, c2, h1, h2, h3 if (!hexBinary) { return base64 } for (i = 0; i < base64.length; i += 2) { c1 = codes.indexOf(base64[i]) c2 = codes.indexOf(base64[i + 1]) if (c1 === -1) { hex = hex.substr(0, hex.length - 1) } else if (c2 === -1) { hex += (c1 >> 2).toString(16) } else { h1 = (c1 >> 2).toString(16) h2 = (((c1 & 0x3) << 2) + (c2 >> 4)).toString(16) h3 = (c2 & 0xF).toString(16) hex += h1 + h2 + h3 } } return hex } let pushStr = function (str, breakBefore, breakAfter, className) { if (!hasBreak && breakBefore) { finalStr += getNL() } if (className && html) { finalStr += '<span class="json-' + className + '">' + str + '</span>' } else { finalStr += str } if (breakAfter) { finalStr += getNL() } hasBreak = breakAfter } let pushJsonValue = function (value, path) { let key, needComma, subpath if (value === undefined) { pushStr(html ? '<em>undefined</em>' : 'undefined', false, false, 'keyword') } else if (value === false) { pushStr(html ? '<em>false</em>' : 'false', false, false, 'keyword') } else if (value === true) { pushStr(html ? '<em>true</em>' : 'true', false, false, 'keyword') } else if (value === null) { pushStr(html ? '<em>null</em>' : 'null', false, false, 'keyword') } else if (typeof value === 'number') { pushStr(String(value), false, false, 'number') } else if (typeof value === 'string') { pushStr(html ? Panel.escape(value) : '\'' + value.replace(/'/g, '\\\'') + '\'', false, false, 'string') } else if (Array.isArray(value) && !value.length) { pushStr('[]') } else if (Array.isArray(value)) { if (html && indentLevel % 2) { pushStr('<span onclick="' + 'this.nextSibling.style.display=\'\';' + 'this.style.display=\'none\'' + '">[<span class="toggle"></span>]</span>' + '<span style="display:none">') } indentLevel++ pushStr('[', false, true) for (key = 0; key < value.length; key++) { if (key) { pushStr(',', false, true) } pushJsonValue(value[key], path) } indentLevel-- pushStr(']', true) if (html && indentLevel % 2) { pushStr('</span>') } } else if (value instanceof ObjectId) { pushStr(html ? formatObjectId(value) : 'ObjectId(\'' + value.$oid + '\')', false, false, 'id') } else if (value instanceof BinData) { if (html) { pushStr(formatBinary(value.$binary), false, false, 'binary') } else { pushStr('BinData(' + value.$type + ', \'' + value.$binary + '\')') } } else if (value instanceof DBRef) { if (html) { pushStr('Ref(<span class="json-string">' + Panel.escape(value.$ref) + '</span>, ' + json.stringify(value.$id, true, false) + ')', false, false, 'keyword') } else { pushStr('DBRef(\'' + value.$ref + '\', ' + json.stringify(value.$id, false, false) + ')') } } else if (value instanceof MinKey) { pushStr('MinKey()', false, false, 'keyword') } else if (value instanceof MaxKey) { pushStr('MaxKey()', false, false, 'keyword') } else if (value instanceof Long) { if (html) { pushStr('Long(<span class="json-number">' + value.$numberLong + '</span>)', false, false, 'keyword') } else { pushStr('NumberLong(\'' + value.$numberLong + '\')') } } else if (value instanceof Date) { pushStr(html ? formatDate(value) : 'ISODate(\'' + value.toISOString() + '\')', false, false, 'date') } else if (value instanceof RegExp) { pushStr(html ? Panel.escape(value) : String(value), false, false, 'regexp') } else if (!Object.keys(value).length) { pushStr('{}') } else { if (html && indentLevel % 2) { pushStr('<span onclick="' + 'this.nextSibling.style.display=\'\';' + 'this.style.display=\'none\'' + '">{<span class="toggle"></span>}</span>' + '<span style="display:none">') } indentLevel++ pushStr('{', false, true) needComma = false Object.keys(value).sort().forEach(key => { if (!needComma) { needComma = true } else { pushStr(',', false, true) } subpath = path ? path + '.' + key : key pushStr(escapeKey(key), false, false, 'field') pushStr(pretty ? ': ' : ':') pushJsonValue(value[key], subpath) }) indentLevel-- pushStr('}', true) if (html && indentLevel % 2) { pushStr('</span>') } } } pushJsonValue(value, '') return finalStr } /** * Return a treated copy of the given value */ json.preParse = function preParse(value) { let r, key if (value instanceof Date) { return { $date: value.getTime() } } else if (value instanceof RegExp) { return { $regex: value.source, $options: (value.global ? 'g' : '') + (value.ignoreCase ? 'i' : '') + (value.multiline ? 'm' : '') } } else if (value === undefined) { return { $undefined: true } } else if (Array.isArray(value)) { return value.map(preParse) } else if (value && typeof value === 'object' && !value.toJSON) { // Simple hash-map r = {} for (key in value) { r[key] = preParse(value[key]) } return r } else if (value === Infinity || value === -Infinity) { return { $infinity: value === Infinity ? 1 : -1 } } else if (Number.isNaN(value)) { return { $nan: 1 } } return value }