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™
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Type: |
+ |
+
+
+ | Date: |
+ |
+
+
+ | Version: |
+ |
+
+
+
+
+
+ | Valid: |
+ until |
+
+
+ | Concurrent users limit: |
+ |
+
+
+
+
+
+ |
+ Last Hour |
+ Last 24 Hours |
+ Last Week |
+ Last Month |
+
+
+ | Maximum: |
+ 0 |
+ 0 |
+ 0 |
+ 0 |
+
+
+ | Minimum: |
+ 0 |
+ 0 |
+ 0 |
+ 0 |
+
+
+ | Average: |
+ 0 |
+ 0 |
+ 0 |
+ 0 |
+
+
+
+
+
+
+
+