diff --git a/public/language/en-GB/admin/dashboard.json b/public/language/en-GB/admin/dashboard.json index 0de31d4917..d864e385f8 100644 --- a/public/language/en-GB/admin/dashboard.json +++ b/public/language/en-GB/admin/dashboard.json @@ -56,8 +56,8 @@ "active-users.total": "Total", "active-users.connections": "Connections", - "anonymous-registered-users": "Anonymous vs Registered Users", - "anonymous": "Anonymous", + "guest-registered-users": "Guest vs Registered Users", + "guest": "Guest", "registered": "Registered", "user-presence": "User Presence", @@ -68,6 +68,7 @@ "unread": "Unread", "high-presence-topics": "High Presence Topics", + "popular-searches": "Popular Searches", "graphs.page-views": "Page Views", "graphs.page-views-registered": "Page Views Registered", @@ -75,7 +76,7 @@ "graphs.page-views-bot": "Page Views Bot", "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", - "graphs.anonymous-users": "Anonymous Users", + "graphs.guest-users": "Guest Users", "last-restarted-by": "Last restarted by", "no-users-browsing": "No users browsing", diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json index 1c5ea73b4f..5b22fbeb36 100644 --- a/public/language/en-GB/admin/menu.json +++ b/public/language/en-GB/admin/menu.json @@ -4,6 +4,7 @@ "dashboard/logins": "Logins", "dashboard/users": "Users", "dashboard/topics": "Topics", + "dashboard/searches": "Searches", "section-general": "General", "section-manage": "Manage", diff --git a/public/less/admin/general/dashboard.less b/public/less/admin/general/dashboard.less index e42c1ef8ac..f7e3f8ff72 100644 --- a/public/less/admin/general/dashboard.less +++ b/public/less/admin/general/dashboard.less @@ -124,7 +124,7 @@ border-color: rgba(151,187,205,1); background-color: rgba(151,187,205,0.2); } - &.anonymous { + &.guest { border-color: #46BFBD; background-color: #5AD3D1; } diff --git a/public/openapi/read.yaml b/public/openapi/read.yaml index 6605079d47..857621dd86 100644 --- a/public/openapi/read.yaml +++ b/public/openapi/read.yaml @@ -78,6 +78,8 @@ paths: $ref: 'read/admin/dashboard/users.yaml' /api/admin/dashboard/topics: $ref: 'read/admin/dashboard/topics.yaml' + /api/admin/dashboard/searches: + $ref: 'read/admin/dashboard/searches.yaml' "/api/admin/settings/{term}": $ref: 'read/admin/settings/term.yaml' /api/admin/settings/languages: diff --git a/public/openapi/read/admin/dashboard/searches.yaml b/public/openapi/read/admin/dashboard/searches.yaml new file mode 100644 index 0000000000..7097c8bcb8 --- /dev/null +++ b/public/openapi/read/admin/dashboard/searches.yaml @@ -0,0 +1,25 @@ +get: + tags: + - admin + summary: Get detailed user registration analytics + responses: + "200": + description: A JSON object containing popular searches. + content: + application/json: + schema: + allOf: + - type: object + properties: + searches: + type: array + items: + type: object + properties: + value: + type: string + description: The string that was searched + score: + type: number + description: Number of times this string has been searched + - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file diff --git a/public/src/admin/dashboard.js b/public/src/admin/dashboard.js index 2a50a09371..645023b563 100644 --- a/public/src/admin/dashboard.js +++ b/public/src/admin/dashboard.js @@ -150,7 +150,7 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func t.translateKey('admin/dashboard:graphs.page-views-bot', []), t.translateKey('admin/dashboard:graphs.unique-visitors', []), t.translateKey('admin/dashboard:graphs.registered-users', []), - t.translateKey('admin/dashboard:graphs.anonymous-users', []), + t.translateKey('admin/dashboard:graphs.guest-users', []), t.translateKey('admin/dashboard:on-categories', []), t.translateKey('admin/dashboard:reading-posts', []), t.translateKey('admin/dashboard:browsing-topics', []), @@ -469,11 +469,11 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func }); } - function updateRegisteredGraph(registered, anonymous) { + function updateRegisteredGraph(registered, guest) { $('#analytics-legend .registered').parent().find('.count').text(registered); - $('#analytics-legend .anonymous').parent().find('.count').text(anonymous); + $('#analytics-legend .guest').parent().find('.count').text(guest); graphs.registered.data.datasets[0].data[0] = registered; - graphs.registered.data.datasets[0].data[1] = anonymous; + graphs.registered.data.datasets[0].data[1] = guest; graphs.registered.update(); } diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index bf47cb9f58..d16d2dbaa6 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -4,6 +4,7 @@ const nconf = require('nconf'); const semver = require('semver'); const winston = require('winston'); const _ = require('lodash'); +const validator = require('validator'); const versions = require('../../admin/versions'); const db = require('../../database'); @@ -18,12 +19,13 @@ const emailer = require('../../emailer'); const dashboardController = module.exports; dashboardController.get = async function (req, res) { - const [stats, notices, latestVersion, lastrestart, isAdmin] = await Promise.all([ + const [stats, notices, latestVersion, lastrestart, isAdmin, popularSearches] = await Promise.all([ getStats(), getNotices(), getLatestVersion(), getLastRestart(), user.isAdministrator(req.uid), + getPopularSearches(), ]); const version = nconf.get('version'); @@ -38,6 +40,7 @@ dashboardController.get = async function (req, res) { canRestart: !!process.send, lastrestart: lastrestart, showSystemControls: isAdmin, + popularSearches: popularSearches, }); }; @@ -238,6 +241,11 @@ async function getLastRestart() { return lastrestart; } +async function getPopularSearches() { + const searches = await db.getSortedSetRevRangeWithScores('searches:all', 0, 9); + return searches.map(s => ({ value: validator.escape(String(s.value)), score: s.score })); +} + dashboardController.getLogins = async (req, res) => { let stats = await getStats(); stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => { @@ -327,3 +335,10 @@ dashboardController.getTopics = async (req, res) => { topics: topicData, }); }; + +dashboardController.getSearches = async (req, res) => { + const searches = await db.getSortedSetRevRangeWithScores('searches:all', 0, 99); + res.render('admin/dashboard/searches', { + searches: searches.map(s => ({ value: validator.escape(String(s.value)), score: s.score })), + }); +}; diff --git a/src/controllers/errors.js b/src/controllers/errors.js index 1f8a401e2e..5677b66007 100644 --- a/src/controllers/errors.js +++ b/src/controllers/errors.js @@ -89,7 +89,9 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl } } catch (_err) { winston.error(`${req.originalUrl}\n${_err.stack}`); - res.status(500).send(_err.message); + if (!res.headersSent) { + res.status(500).send(_err.message); + } } }; diff --git a/src/routes/admin.js b/src/routes/admin.js index 97ecb66d7e..43c427dab9 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -11,6 +11,7 @@ module.exports = function (app, name, middleware, controllers) { helpers.setupAdminPageRoute(app, `/${name}/dashboard/logins`, middleware, middlewares, controllers.admin.dashboard.getLogins); helpers.setupAdminPageRoute(app, `/${name}/dashboard/users`, middleware, middlewares, controllers.admin.dashboard.getUsers); helpers.setupAdminPageRoute(app, `/${name}/dashboard/topics`, middleware, middlewares, controllers.admin.dashboard.getTopics); + helpers.setupAdminPageRoute(app, `/${name}/dashboard/searches`, middleware, middlewares, controllers.admin.dashboard.getSearches); helpers.setupAdminPageRoute(app, `/${name}/manage/categories`, middleware, middlewares, controllers.admin.categories.getAll); helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id`, middleware, middlewares, controllers.admin.categories.get); diff --git a/src/search.js b/src/search.js index 99fae633e8..f54181fb52 100644 --- a/src/search.js +++ b/src/search.js @@ -45,6 +45,7 @@ async function searchInContent(data) { async function doSearch(type, searchIn) { if (searchIn.includes(data.searchIn)) { + await recordSearch(data.query); return await plugins.hooks.fire('filter:search.query', { index: type, content: data.query, @@ -94,6 +95,13 @@ async function searchInContent(data) { return Object.assign(returnData, metadata); } +async function recordSearch(query) { + const cleanedQuery = String(query).trim().toLowerCase().substr(0, 255); + if (cleanedQuery.length > 2) { + await db.sortedSetIncrBy('searches:all', 1, cleanedQuery); + } +} + async function filterAndSort(pids, data) { if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) { return pids; diff --git a/src/views/admin/dashboard.tpl b/src/views/admin/dashboard.tpl index 26d64231b5..37649a8bc7 100644 --- a/src/views/admin/dashboard.tpl +++ b/src/views/admin/dashboard.tpl @@ -4,22 +4,22 @@
-
+
-
[[admin/dashboard:anonymous-registered-users]]
+
[[admin/dashboard:guest-registered-users]]
  • () [[admin/dashboard:registered]]
  • -
  • () [[admin/dashboard:anonymous]]
  • +
  • () [[admin/dashboard:guest]]
-
+
[[admin/dashboard:user-presence]]
@@ -36,7 +36,7 @@
-
+
[[admin/dashboard:high-presence-topics]]
@@ -47,6 +47,20 @@
+
+
+
[[admin/dashboard:popular-searches]]
+
+
+ +
+
+
+
diff --git a/src/views/admin/dashboard/searches.tpl b/src/views/admin/dashboard/searches.tpl new file mode 100644 index 0000000000..baefcea306 --- /dev/null +++ b/src/views/admin/dashboard/searches.tpl @@ -0,0 +1,25 @@ +
+
+ + + [[admin/dashboard:back-to-dashboard]] + + + + + + {{{ if !searches.length}}} + + + + {{{ end }}} + {{{ each searches }}} + + + + + {{{ end }}} + +
[[admin/dashboard:details.no-searches]]
{searches.value}{searches.score}
+
+
\ No newline at end of file diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl index f519ecd475..343ba8445c 100644 --- a/src/views/admin/partials/menu.tpl +++ b/src/views/admin/partials/menu.tpl @@ -184,6 +184,7 @@
  • [[admin/menu:dashboard/logins]]
  • [[admin/menu:dashboard/users]]
  • [[admin/menu:dashboard/topics]]
  • +
  • [[admin/menu:dashboard/searches]]
  • {{{ end }}}