diff --git a/Common/config/production-linux.json b/Common/config/production-linux.json index 2f4a42f1..fcfaccb6 100644 --- a/Common/config/production-linux.json +++ b/Common/config/production-linux.json @@ -27,6 +27,10 @@ "path": "/var/www/onlyoffice/documentserver/server/welcome", "options": {"maxAge": "7d"} }, + "/info": { + "path": "/var/www/onlyoffice/documentserver/server/info", + "options": {"maxAge": "7d"} + }, "/sdkjs-plugins": { "path": "/var/www/onlyoffice/documentserver/sdkjs-plugins", "options": {"maxAge": "7d"} diff --git a/Common/config/production-windows.json b/Common/config/production-windows.json index 77cfde6f..296d9ac3 100644 --- a/Common/config/production-windows.json +++ b/Common/config/production-windows.json @@ -30,6 +30,10 @@ "/welcome": { "path": "../../welcome", "options": {"maxAge": "7d"} + }, + "/info": { + "path": "../../info", + "options": {"maxAge": "7d"} } } }, diff --git a/Common/sources/constants.js b/Common/sources/constants.js index c8f0e8fa..ab802950 100644 --- a/Common/sources/constants.js +++ b/Common/sources/constants.js @@ -206,6 +206,7 @@ exports.REDIS_KEY_SHUTDOWN = 'shutdown'; exports.REDIS_KEY_COLLECT_LOST = 'collectlost'; exports.REDIS_KEY_LICENSE = 'license'; exports.REDIS_KEY_LICENSE_T = 'licenseT'; +exports.REDIS_KEY_EDITOR_CONNECTIONS = 'editorconnections'; exports.SHUTDOWN_CODE = 4001; exports.SHUTDOWN_REASON = 'server shutdown'; diff --git a/Common/sources/license.js b/Common/sources/license.js index 0ccea3e1..d30fab11 100644 --- a/Common/sources/license.js +++ b/Common/sources/license.js @@ -65,7 +65,9 @@ exports.readLicense = function*() { usersCount: 0, usersExpire: constants.LICENSE_EXPIRE_USERS_ONE_DAY, hasLicense: false, - plugins: false + plugins: false, + buildDate: oBuildDate, + endDate: null }; let checkFile = false; try { @@ -80,6 +82,7 @@ exports.readLicense = function*() { const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRhGF7X4A0ZVlEg594WmODVVUI\niiPQs04aLmvfg8SborHss5gQXu0aIdUT6nb5rTh5hD2yfpF2WIW6M8z0WxRhwicg\nXwi80H1aLPf6lEPPLvN29EhQNjBpkFkAJUbS8uuhJEeKw0cE49g80eBBF4BCqSL6\nPFQbP9/rByxdxEoAIQIDAQAB\n-----END PUBLIC KEY-----\n'; if (verify.verify(publicKey, sign, 'hex')) { const endDate = new Date(oLicense['end_date']); + res.endDate = endDate; const isTrial = (true === oLicense['trial'] || 'true' === oLicense['trial']); // Someone who likes to put json string instead of bool res.mode = isTrial ? c_LM.Trial : getLicenseMode(oLicense['mode']); const checkDate = c_LM.Trial === res.mode ? new Date() : oBuildDate; diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index 5e01400a..5a6c9723 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -159,6 +159,7 @@ const redisKeyForceSaveTimer = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_T const redisKeyForceSaveTimerLock = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_TIMER_LOCK; const redisKeySaved = cfgRedisPrefix + constants.REDIS_KEY_SAVED; const redisKeyPresenceUniqueUsers = cfgRedisPrefix + constants.REDIS_KEY_PRESENCE_UNIQUE_USERS; +const redisKeyEditorConnections = cfgRedisPrefix + constants.REDIS_KEY_EDITOR_CONNECTIONS; const EditorTypes = { document : 0, @@ -181,6 +182,10 @@ const FORCE_SAVE_EXPIRATION = Math.min(Math.max(cfgForceSaveInterval, MIN_SAVE_E cfgQueueRetentionPeriod * 1000); const HEALTH_CHECK_KEY_MAX = 10000; +const PRECISION = [{name: 'hour', val: ms('1h')}, {name: 'day', val: ms('1d')}, {name: 'week', val: ms('7d')}, + {name: 'month', val: ms('31d')}, +]; + function getIsShutdown() { return shutdownFlag; } @@ -2952,6 +2957,19 @@ exports.install = function(server, callbackFunction) { } }); } + + function* collectStats(countEdit, countView) { + let now = Date.now(); + var multi = redisClient.multi( + [ + ['lpop', redisKeyEditorConnections], + ['rpush', redisKeyEditorConnections, JSON.stringify({time: now, edit: countEdit, view: countView})] + ]); + let multiRes = yield utils.promiseRedis(multi, multi.exec); + if (multiRes.length > 1 && JSON.parse(multiRes[0]).time > now - PRECISION[PRECISION.length - 1].val) { + yield utils.promiseRedis(redisClient, redisClient.lpush, redisKeyEditorConnections, multiRes[0]); + } + } function expireDoc() { var cronJob = this; return co(function* () { @@ -3006,6 +3024,7 @@ exports.install = function(server, callbackFunction) { idSet.forEach(function(value1, value2, set) { commands.push(['zadd', redisKeyDocuments, expireAt, value1]); }); + yield* collectStats(countEdit, countView); if (commands.length > 0) { var multi = redisClient.multi(commands); yield utils.promiseRedis(multi, multi.exec); @@ -3093,6 +3112,80 @@ exports.healthCheck = function(req, res) { } }); }; +exports.licenseInfo = function(req, res) { + return co(function*() { + let isError = false; + let output = { + connectionsStat: {}, licenseInfo: {}, serverInfo: { + buildVersion: commonDefines.buildVersion, buildNumber: commonDefines.buildNumber, + } + }; + Object.assign(output.licenseInfo, licenseInfo); + try { + logger.debug('licenseInfo start'); + var precisionSum = {}; + for (let i = 0; i < PRECISION.length; ++i) { + precisionSum[PRECISION[i].name] = { + edit: {min: Number.MAX_VALUE, sum: 0, count: 0, max: 0, time: null, period: PRECISION[i].val}, + view: {min: Number.MAX_VALUE, sum: 0, count: 0, max: 0} + }; + output.connectionsStat[PRECISION[i].name] = { + edit: {min: 0, avr: 0, max: 0}, + view: {min: 0, avr: 0, max: 0} + }; + } + var redisRes = yield utils.promiseRedis(redisClient, redisClient.lrange, redisKeyEditorConnections, 0, -1); + const now = Date.now(); + var precisionIndex = 0; + for (let i = redisRes.length - 1; i >= 1; i -= 2) { + for (let j = precisionIndex; j < PRECISION.length; ++j) { + let elem = JSON.parse(redisRes[i]); + if (now - elem.time < PRECISION[j].val) { + let precision = precisionSum[PRECISION[j].name]; + precision.edit.min = Math.min(precision.edit.min, elem.edit); + precision.edit.max = Math.max(precision.edit.max, elem.edit); + precision.edit.sum += elem.edit; + precision.edit.count++; + precision.edit.time = elem.time; + precision.view.min = Math.min(precision.view.min, elem.view); + precision.view.max = Math.max(precision.view.max, elem.view); + precision.view.sum += elem.view; + precision.view.count++; + } else { + precisionIndex = j + 1; + } + } + } + for (let i in precisionSum) { + let precision = precisionSum[i]; + let precisionOut = output.connectionsStat[i]; + //scale compensates for the lack of points at server start + let scale = (now - precision.edit.time) / precision.edit.period; + if (precision.edit.count > 0) { + precisionOut.edit.avr = Math.round((precision.edit.sum / precision.edit.count) * scale); + precisionOut.edit.min = precision.edit.min; + precisionOut.edit.max = precision.edit.max; + } + if (precision.view.count > 0) { + precisionOut.view.avr = Math.round((precision.view.sum / precision.view.count) * scale); + precisionOut.view.min = precision.view.min; + precisionOut.view.max = precision.view.max; + } + } + logger.debug('licenseInfo end'); + } catch (err) { + isError = true; + logger.error('licenseInfo error\r\n%s', err.stack); + } finally { + if (!isError) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(output)); + } else { + res.sendStatus(400); + } + } + }); +}; // Команда с сервера (в частности teamlab) exports.commandFromServer = function (req, res) { return co(function* () { diff --git a/DocService/sources/server.js b/DocService/sources/server.js index f776533e..ca845b27 100644 --- a/DocService/sources/server.js +++ b/DocService/sources/server.js @@ -234,6 +234,7 @@ if (cluster.isMaster) { } converterService.builder(req, res); }); + app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo); const sendUserPlugins = (res, data) => { res.setHeader('Content-Type', 'application/json'); diff --git a/Makefile b/Makefile index 01e8af9c..6bf3e7d2 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,10 @@ WELCOME_DIR = welcome WELCOME_FILES = $(WELCOME_DIR)/** WELCOME = $(OUTPUT)/$(WELCOME_DIR)/ +INFO_DIR = info +INFO_FILES = $(INFO_DIR)/** +INFO = $(OUTPUT)/$(INFO_DIR)/ + CORE_FONTS_DIR = core-fonts CORE_FONTS_FILES = ../$(CORE_FONTS_DIR)/** CORE_FONTS = $(OUTPUT)/../$(CORE_FONTS_DIR)/ @@ -100,7 +104,7 @@ CORE_FONTS = $(OUTPUT)/../$(CORE_FONTS_DIR)/ .PHONY: all clean install uninstall build-date htmlfileinternal docbuilder .NOTPARALLEL: -all: $(FILE_CONVERTER) $(SPELLCHECKER_DICTIONARIES) $(TOOLS) $(SCHEMA) $(CORE_FONTS) $(LICENSE) $(WELCOME) build-date +all: $(FILE_CONVERTER) $(SPELLCHECKER_DICTIONARIES) $(TOOLS) $(SCHEMA) $(CORE_FONTS) $(LICENSE) $(WELCOME) $(INFO) build-date ext: htmlfileinternal docbuilder @@ -147,6 +151,10 @@ $(WELCOME): mkdir -p $(WELCOME) && \ cp -r -t $(WELCOME) $(WELCOME_FILES) +$(INFO): + mkdir -p $(INFO) && \ + cp -r -t $(INFO) $(INFO_FILES) + $(CORE_FONTS): mkdir -p $(CORE_FONTS) && \ cp -r -t $(CORE_FONTS) $(CORE_FONTS_FILES) diff --git a/info/img/favicon.ico b/info/img/favicon.ico new file mode 100644 index 00000000..fc55efad Binary files /dev/null and b/info/img/favicon.ico differ diff --git a/info/img/icon-cross.png b/info/img/icon-cross.png new file mode 100644 index 00000000..64012256 Binary files /dev/null and b/info/img/icon-cross.png differ diff --git a/info/img/logo.png b/info/img/logo.png new file mode 100644 index 00000000..27bab738 Binary files /dev/null and b/info/img/logo.png differ diff --git a/info/index.html b/info/index.html new file mode 100644 index 00000000..54380863 --- /dev/null +++ b/info/index.html @@ -0,0 +1,274 @@ + + + + ONLYOFFICE™ + + + + + + + +
+ ONLYOFFICE +
+
+
Please, wait...
+ + +
+ + +