mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
refactor: cleanup ip:recent
This commit is contained in:
@@ -41,9 +41,6 @@
|
|||||||
"sockets.default-placeholder": "Default: %1",
|
"sockets.default-placeholder": "Default: %1",
|
||||||
"sockets.delay": "Reconnection Delay",
|
"sockets.delay": "Reconnection Delay",
|
||||||
|
|
||||||
"analytics.settings": "Analytics Settings",
|
|
||||||
"analytics.max-cache": "Analytics Cache Max Value",
|
|
||||||
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
|
|
||||||
"compression.settings": "Compression Settings",
|
"compression.settings": "Compression Settings",
|
||||||
"compression.enable": "Enable Compression",
|
"compression.enable": "Enable Compression",
|
||||||
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."
|
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ get:
|
|||||||
latestVersion:
|
latestVersion:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
hideAllTime:
|
||||||
|
type: boolean
|
||||||
upgradeAvailable:
|
upgradeAvailable:
|
||||||
type: boolean
|
type: boolean
|
||||||
nullable: true
|
nullable: true
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
const cronJob = require('cron').CronJob;
|
const cronJob = require('cron').CronJob;
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
const crypto = require('crypto');
|
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
@@ -12,36 +11,24 @@ const sleep = util.promisify(setTimeout);
|
|||||||
const db = require('./database');
|
const db = require('./database');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const plugins = require('./plugins');
|
const plugins = require('./plugins');
|
||||||
const meta = require('./meta');
|
|
||||||
const pubsub = require('./pubsub');
|
const pubsub = require('./pubsub');
|
||||||
const cacheCreate = require('./cache/lru');
|
|
||||||
|
|
||||||
const Analytics = module.exports;
|
const Analytics = module.exports;
|
||||||
|
|
||||||
const secret = nconf.get('secret');
|
|
||||||
|
|
||||||
let local = {
|
let local = {
|
||||||
counters: {},
|
counters: {},
|
||||||
pageViews: 0,
|
pageViews: 0,
|
||||||
pageViewsRegistered: 0,
|
pageViewsRegistered: 0,
|
||||||
pageViewsGuest: 0,
|
pageViewsGuest: 0,
|
||||||
pageViewsBot: 0,
|
pageViewsBot: 0,
|
||||||
uniqueIPCount: 0,
|
|
||||||
uniquevisitors: 0,
|
uniquevisitors: 0,
|
||||||
};
|
};
|
||||||
const empty = _.cloneDeep(local);
|
const empty = _.cloneDeep(local);
|
||||||
const total = _.cloneDeep(local);
|
const total = _.cloneDeep(local);
|
||||||
|
|
||||||
let ipCache;
|
|
||||||
|
|
||||||
const runJobs = nconf.get('runJobs');
|
const runJobs = nconf.get('runJobs');
|
||||||
|
|
||||||
Analytics.init = async function () {
|
Analytics.init = async function () {
|
||||||
ipCache = cacheCreate({
|
|
||||||
max: parseInt(meta.config['analytics:maxCache'], 10) || 500,
|
|
||||||
ttl: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
new cronJob('*/10 * * * * *', (async () => {
|
new cronJob('*/10 * * * * *', (async () => {
|
||||||
publishLocalAnalytics();
|
publishLocalAnalytics();
|
||||||
if (runJobs) {
|
if (runJobs) {
|
||||||
@@ -50,6 +37,14 @@ Analytics.init = async function () {
|
|||||||
}
|
}
|
||||||
}), null, true);
|
}), null, true);
|
||||||
|
|
||||||
|
if (runJobs) {
|
||||||
|
new cronJob('*/30 * * * *', (async () => {
|
||||||
|
const cutoff = Date.now() - 172800000;
|
||||||
|
const ips = await db.getSortedSetRangeByScore('ip:recent', 0, 500, '-inf', cutoff);
|
||||||
|
await db.sortedSetRemove('ip:recent', ips);
|
||||||
|
}), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (runJobs) {
|
if (runJobs) {
|
||||||
pubsub.on('analytics:publish', (data) => {
|
pubsub.on('analytics:publish', (data) => {
|
||||||
incrementProperties(total, data.local);
|
incrementProperties(total, data.local);
|
||||||
@@ -106,22 +101,17 @@ Analytics.pageView = async function (payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payload.ip) {
|
if (payload.ip) {
|
||||||
// Retrieve hash or calculate if not present
|
const score = await db.sortedSetScore('ip:recent', payload.ip);
|
||||||
let hash = ipCache.get(payload.ip + secret);
|
let record = !score;
|
||||||
if (!hash) {
|
if (score) {
|
||||||
hash = crypto.createHash('sha1').update(payload.ip + secret).digest('hex');
|
|
||||||
ipCache.set(payload.ip + secret, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
const score = await db.sortedSetScore('ip:recent', hash);
|
|
||||||
if (!score) {
|
|
||||||
local.uniqueIPCount += 1;
|
|
||||||
}
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(today.getHours(), 0, 0, 0);
|
today.setHours(today.getHours(), 0, 0, 0);
|
||||||
if (!score || score < today.getTime()) {
|
record = score < today.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record) {
|
||||||
local.uniquevisitors += 1;
|
local.uniquevisitors += 1;
|
||||||
await db.sortedSetAdd('ip:recent', Date.now(), hash);
|
await db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -176,11 +166,6 @@ Analytics.writeData = async function () {
|
|||||||
total.uniquevisitors = 0;
|
total.uniquevisitors = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total.uniqueIPCount > 0) {
|
|
||||||
dbQueue.push(db.incrObjectFieldBy('global', 'uniqueIPCount', total.uniqueIPCount));
|
|
||||||
total.uniqueIPCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(total.counters)) {
|
for (const [key, value] of Object.entries(total.counters)) {
|
||||||
incrByBulk.push([`analytics:${key}`, value, today.getTime()]);
|
incrByBulk.push([`analytics:${key}`, value, today.getTime()]);
|
||||||
metrics.push(key);
|
metrics.push(key);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ dashboardController.get = async function (req, res) {
|
|||||||
lastrestart: lastrestart,
|
lastrestart: lastrestart,
|
||||||
showSystemControls: isAdmin,
|
showSystemControls: isAdmin,
|
||||||
popularSearches: popularSearches,
|
popularSearches: popularSearches,
|
||||||
|
hideAllTime: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ async function getStats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let results = await Promise.all([
|
let results = await Promise.all([
|
||||||
getStatsFromAnalytics('uniquevisitors', 'uniqueIPCount'),
|
getStatsFromAnalytics('uniquevisitors', ''),
|
||||||
getStatsFromAnalytics('logins', 'loginCount'),
|
getStatsFromAnalytics('logins', 'loginCount'),
|
||||||
getStatsForSet('users:joindate', 'userCount'),
|
getStatsForSet('users:joindate', 'userCount'),
|
||||||
getStatsForSet('posts:pid', 'postCount'),
|
getStatsForSet('posts:pid', 'postCount'),
|
||||||
@@ -227,6 +228,7 @@ function calculateDeltas(results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getGlobalField(field) {
|
async function getGlobalField(field) {
|
||||||
|
if (!field) return 0;
|
||||||
const count = await db.getObjectField('global', field);
|
const count = await db.getObjectField('global', field);
|
||||||
return parseInt(count, 10) || 0;
|
return parseInt(count, 10) || 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
<div class="table-responsive mb-3">
|
<div class="table-responsive mb-3">
|
||||||
<table class="table text-sm">
|
<table class="table">
|
||||||
<thead>
|
<thead class="text-xs">
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.yesterday]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.yesterday]]</th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.today]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.today]]</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.last-week]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-week]]</th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.this-week]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-week]]</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.last-month]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.last-month]]</th>
|
||||||
<th class="text-end">[[admin/dashboard:stats.this-month]]</th>
|
<th class="text-end text-nowrap">[[admin/dashboard:stats.this-month]]</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
{{{ if !hideAllTime}}}
|
||||||
<th class="text-end">[[admin/dashboard:stats.all]]</th>
|
<th class="text-end">[[admin/dashboard:stats.all]]</th>
|
||||||
|
{{{ end }}}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="text-sm">
|
||||||
{{{ each stats }}}
|
{{{ each stats }}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td class="fw-bold text-nowrap">
|
||||||
<strong>
|
|
||||||
{{{ if ./href }}}
|
{{{ if ./href }}}
|
||||||
<a href="{./href}">{./name}</a>
|
<a href="{./href}">{./name}</a>
|
||||||
{{{ else }}}
|
{{{ else }}}
|
||||||
{./name}
|
{./name}
|
||||||
{{{ end }}}
|
{{{ end }}}
|
||||||
</strong>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">{formattedNumber(./yesterday)}</td>
|
<td class="text-end">{formattedNumber(./yesterday)}</td>
|
||||||
<td class="text-end">{formattedNumber(./today)}</td>
|
<td class="text-end">{formattedNumber(./today)}</td>
|
||||||
@@ -38,8 +40,9 @@
|
|||||||
<td class="text-end">{formattedNumber(./lastmonth)}</td>
|
<td class="text-end">{formattedNumber(./lastmonth)}</td>
|
||||||
<td class="text-end">{formattedNumber(./thismonth)}</td>
|
<td class="text-end">{formattedNumber(./thismonth)}</td>
|
||||||
<td class="{./monthTextClass}"><small>{./monthIncrease}%</small></td>
|
<td class="{./monthTextClass}"><small>{./monthIncrease}%</small></td>
|
||||||
|
{{{ if !hideAllTime}}}
|
||||||
<td class="text-end">{formattedNumber(./alltime)}</td>
|
<td class="text-end">{formattedNumber(./alltime)}</td>
|
||||||
|
{{{ end }}}
|
||||||
</tr>
|
</tr>
|
||||||
{{{ end }}}
|
{{{ end }}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -146,20 +146,6 @@
|
|||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<div id="analytics-settings" class="mb-4">
|
|
||||||
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:analytics.settings]]</h5>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label" for="analytics:maxCache">[[admin/settings/advanced:analytics.max-cache]]</label>
|
|
||||||
<input class="form-control" id="analytics:maxCache" type="text" value="500" placeholder="500" data-field="analytics:maxCache" />
|
|
||||||
<p class="form-text">
|
|
||||||
[[admin/settings/advanced:analytics.max-cache-help]]
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<div id="compression-settings" class="mb-4">
|
<div id="compression-settings" class="mb-4">
|
||||||
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:compression.settings]]</h5>
|
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/advanced:compression.settings]]</h5>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user