mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-10 23:31:51 +02:00
Merge commit 'f7e43877482f6efc276ea71e61d7c7524a8f5a44' into v1.x.x
This commit is contained in:
6
nodebb
6
nodebb
@@ -118,8 +118,12 @@ var getRunningPid = function(callback) {
|
||||
version: async.apply(getCurrentVersion)
|
||||
}),
|
||||
function(payload, next) {
|
||||
var toCheck = Object.keys(payload.plugins);
|
||||
if (!payload.plugins.length) {
|
||||
process.stdout.write('OK'.green + '\n'.reset);
|
||||
return next(null, []); // no extraneous plugins installed
|
||||
}
|
||||
|
||||
var toCheck = Object.keys(payload.plugins);
|
||||
request({
|
||||
method: 'GET',
|
||||
url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='),
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"express-session": "^1.8.2",
|
||||
"express-useragent": "0.2.4",
|
||||
"html-to-text": "2.0.0",
|
||||
"ip": "1.1.2",
|
||||
"jimp": "0.2.21",
|
||||
"less": "^2.0.0",
|
||||
"logrotate-stream": "^0.2.3",
|
||||
@@ -43,7 +44,7 @@
|
||||
"mongodb": "~2.1.3",
|
||||
"morgan": "^1.3.2",
|
||||
"nconf": "~0.8.2",
|
||||
"nodebb-plugin-composer-default": "3.0.7",
|
||||
"nodebb-plugin-composer-default": "3.0.10",
|
||||
"nodebb-plugin-dbsearch": "1.0.0",
|
||||
"nodebb-plugin-emoji-extended": "1.0.3",
|
||||
"nodebb-plugin-markdown": "4.0.17",
|
||||
@@ -52,9 +53,9 @@
|
||||
"nodebb-plugin-spam-be-gone": "0.4.5",
|
||||
"nodebb-rewards-essentials": "0.0.8",
|
||||
"nodebb-theme-lavender": "3.0.9",
|
||||
"nodebb-theme-persona": "4.0.93",
|
||||
"nodebb-theme-vanilla": "5.0.54",
|
||||
"nodebb-widget-essentials": "2.0.7",
|
||||
"nodebb-theme-persona": "4.0.99",
|
||||
"nodebb-theme-vanilla": "5.0.56",
|
||||
"nodebb-widget-essentials": "2.0.8",
|
||||
"nodemailer": "2.0.0",
|
||||
"nodemailer-sendmail-transport": "1.0.0",
|
||||
"nodemailer-smtp-transport": "^2.4.1",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
"user-banned": "User banned",
|
||||
"user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
|
||||
"blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.",
|
||||
|
||||
"no-category": "Category does not exist",
|
||||
"no-topic": "Topic does not exist",
|
||||
|
||||
@@ -103,6 +103,8 @@
|
||||
"enable_topic_searching": "Enable In-Topic Searching",
|
||||
"topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen",
|
||||
|
||||
"scroll_to_my_post": "After posting a reply, show the new post",
|
||||
|
||||
"follow_topics_you_reply_to": "Follow topics that you reply to",
|
||||
"follow_topics_you_create": "Follow topics you create",
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
@import "./settings";
|
||||
|
||||
@import "../flags";
|
||||
@import "../blacklist";
|
||||
|
||||
@import "./modules/alerts";
|
||||
@import "./modules/selectable";
|
||||
@@ -238,5 +239,5 @@
|
||||
}
|
||||
|
||||
[class^="col-"] .mdl-switch__label {
|
||||
padding-right: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
6
public/less/blacklist.less
Normal file
6
public/less/blacklist.less
Normal file
@@ -0,0 +1,6 @@
|
||||
#blacklist-rules {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
display: block;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
@@ -152,28 +152,45 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri
|
||||
* @param parentId {number} parent category identifier
|
||||
*/
|
||||
function renderList(categories, container, parentId){
|
||||
templates.parse('admin/partials/categories/category-rows', {
|
||||
cid: parentId,
|
||||
categories: categories
|
||||
}, function(html) {
|
||||
container.append(html);
|
||||
// Translate category names if needed
|
||||
var count = 0;
|
||||
categories.forEach(function(category, idx, parent) {
|
||||
translator.translate(category.name, function(translated) {
|
||||
if (category.name !== translated) {
|
||||
category.name = translated;
|
||||
}
|
||||
++count;
|
||||
|
||||
// Handle and children categories in this level have
|
||||
for(var x=0,numCategories=categories.length;x<numCategories;x++) {
|
||||
renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid);
|
||||
}
|
||||
|
||||
// Make list sortable
|
||||
sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], {
|
||||
group: 'cross-categories',
|
||||
animation: 150,
|
||||
handle: '.icon',
|
||||
dataIdAttr: 'data-cid',
|
||||
ghostClass: "placeholder",
|
||||
onAdd: itemDidAdd,
|
||||
onEnd: itemDragDidEnd
|
||||
if (count === parent.length) {
|
||||
continueRender();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function continueRender() {
|
||||
templates.parse('admin/partials/categories/category-rows', {
|
||||
cid: parentId,
|
||||
categories: categories
|
||||
}, function(html) {
|
||||
container.append(html);
|
||||
|
||||
// Handle and children categories in this level have
|
||||
for(var x=0,numCategories=categories.length;x<numCategories;x++) {
|
||||
renderList(categories[x].children, $('li[data-cid="' + categories[x].cid + '"]'), categories[x].cid);
|
||||
}
|
||||
|
||||
// Make list sortable
|
||||
sortables[parentId] = Sortable.create($('ul[data-cid="' + parentId + '"]')[0], {
|
||||
group: 'cross-categories',
|
||||
animation: 150,
|
||||
handle: '.icon',
|
||||
dataIdAttr: 'data-cid',
|
||||
ghostClass: "placeholder",
|
||||
onAdd: itemDidAdd,
|
||||
onEnd: itemDragDidEnd
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Categories;
|
||||
|
||||
@@ -155,6 +155,12 @@ define('admin/manage/category', [
|
||||
} else {
|
||||
$('a[href="#analytics"]').on('shown.bs.tab', Category.setupGraphs);
|
||||
}
|
||||
|
||||
// Fix the input field for the category name, as it can contain a translation key
|
||||
var nameInput = $('#cid-' + ajaxify.data.cid + '-name');
|
||||
if (ajaxify.data.category.name !== nameInput.val()) {
|
||||
$('#cid-' + ajaxify.data.category.cid + '-name').val(ajaxify.data.category.name);
|
||||
}
|
||||
};
|
||||
|
||||
Category.setupPrivilegeTable = function() {
|
||||
|
||||
40
public/src/admin/manage/ip-blacklist.js
Normal file
40
public/src/admin/manage/ip-blacklist.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
/* globals $, app, socket, templates, define, bootbox */
|
||||
|
||||
define('admin/manage/ip-blacklist', [], function() {
|
||||
|
||||
var Blacklist = {};
|
||||
|
||||
Blacklist.init = function() {
|
||||
var blacklist = $('#blacklist-rules');
|
||||
|
||||
blacklist.on('keyup', function() {
|
||||
$('#blacklist-rules-holder').val(blacklist.val());
|
||||
});
|
||||
|
||||
$('[data-action="apply"]').on('click', function() {
|
||||
socket.emit('blacklist.save', blacklist.val(), function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
app.alert({
|
||||
type: 'success',
|
||||
alert_id: 'blacklist-saved',
|
||||
title: 'Blacklist Applied',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('[data-action="test"]').on('click', function() {
|
||||
socket.emit('blacklist.validate', {
|
||||
rules: blacklist.val()
|
||||
}, function(err, data) {
|
||||
templates.parse('admin/partials/blacklist-validate', data, function(html) {
|
||||
bootbox.alert(html);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return Blacklist;
|
||||
});
|
||||
@@ -22,10 +22,16 @@ $(document).ready(function() {
|
||||
$(window).on('popstate', function (ev) {
|
||||
ev = ev.originalEvent;
|
||||
|
||||
if (ev !== null && ev.state && ev.state.url !== undefined) {
|
||||
ajaxify.go(ev.state.url, function() {
|
||||
$(window).trigger('action:popstate', {url: ev.state.url});
|
||||
}, true);
|
||||
if (ev !== null && ev.state) {
|
||||
if (ev.state.url === null && ev.state.returnPath !== undefined) {
|
||||
window.history.replaceState({
|
||||
url: ev.state.returnPath
|
||||
}, ev.state.returnPath, config.relative_path + '/' + ev.state.returnPath);
|
||||
} else if (ev.state.url !== undefined) {
|
||||
ajaxify.go(ev.state.url, function() {
|
||||
$(window).trigger('action:popstate', {url: ev.state.url});
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -148,6 +154,7 @@ $(document).ready(function() {
|
||||
|
||||
templates.parse(tpl_url, data, function(template) {
|
||||
translator.translate(template, function(translatedTemplate) {
|
||||
translatedTemplate = translator.unescape(translatedTemplate);
|
||||
$('body').addClass(data.bodyClass);
|
||||
$('#content').html(translatedTemplate);
|
||||
|
||||
|
||||
@@ -521,6 +521,7 @@ app.cacheBuster = null;
|
||||
if (typeof blockName === 'string') {
|
||||
templates.parse(template, blockName, data, function(html) {
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
translatedHTML = translator.unescape(translatedHTML);
|
||||
callback($(translatedHTML));
|
||||
});
|
||||
});
|
||||
@@ -528,6 +529,7 @@ app.cacheBuster = null;
|
||||
callback = data, data = blockName;
|
||||
templates.parse(template, data, function(html) {
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
translatedHTML = translator.unescape(translatedHTML);
|
||||
callback($(translatedHTML));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,8 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'],
|
||||
$('#inputBirthday').datepicker({
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
yearRange: '1900:+0'
|
||||
yearRange: '1900:-5y',
|
||||
defaultDate: '-13y'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ define('forum/topic/events', [
|
||||
}
|
||||
|
||||
editedPostEl.fadeOut(250, function() {
|
||||
editedPostEl.html(data.post.content);
|
||||
editedPostEl.html(translator.unescape(data.post.content));
|
||||
editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive');
|
||||
app.replaceSelfLinks(editedPostEl.find('a'));
|
||||
posts.wrapImagesInLinks(editedPostEl.parent());
|
||||
@@ -186,7 +186,7 @@ define('forum/topic/events', [
|
||||
if (isDeleted) {
|
||||
postEl.find('[component="post/content"]').translateHtml('[[topic:post_is_deleted]]');
|
||||
} else {
|
||||
postEl.find('[component="post/content"]').html(data.content);
|
||||
postEl.find('[component="post/content"]').html(translator.unescape(data.content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,12 @@ define('forum/topic/posts', [
|
||||
ajaxify.data.postcount ++;
|
||||
postTools.updatePostCount(ajaxify.data.postcount);
|
||||
|
||||
if (config.usePagination) {
|
||||
onNewPostPagination(data);
|
||||
} else {
|
||||
onNewPostInfiniteScroll(data);
|
||||
if (ajaxify.data.scrollToMyPost) {
|
||||
if (config.usePagination) {
|
||||
onNewPostPagination(data);
|
||||
} else {
|
||||
onNewPostInfiniteScroll(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,7 +142,7 @@ define('forum/topic/posts', [
|
||||
}
|
||||
|
||||
data.slug = ajaxify.data.slug;
|
||||
|
||||
|
||||
$(window).trigger('action:posts.loading', {posts: data.posts, after: after, before: before});
|
||||
|
||||
app.parseAndTranslate('topic', 'posts', data, function(html) {
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
'<i style="color:' + child.color + ';" class="fa fa-stack-1x ' + child.icon + '"></i>' +
|
||||
'</span><small>' + child.name + '</small></a> ';
|
||||
});
|
||||
html = html ? ('<br/><span class="category-children">' + html + '</span>') : html;
|
||||
html = html ? ('<span class="category-children">' + html + '</span>') : html;
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ module.exports = function(Categories) {
|
||||
db.incrObjectField('global', 'nextCid', next);
|
||||
},
|
||||
function(cid, next) {
|
||||
var slug = cid + '/' + utils.slugify(data.name),
|
||||
order = data.order || cid, // If no order provided, place it at the end
|
||||
colours = Categories.assignColours();
|
||||
var slug = cid + '/' + utils.slugify(data.name);
|
||||
var order = data.order || cid; // If no order provided, place it at the end
|
||||
var colours = Categories.assignColours();
|
||||
|
||||
category = {
|
||||
cid: cid,
|
||||
name: data.name,
|
||||
description: ( data.description ? data.description : '' ),
|
||||
icon: ( data.icon ? data.icon : '' ),
|
||||
description: data.description ? data.description : '',
|
||||
icon: data.icon ? data.icon : '',
|
||||
bgColor: data.bgColor || colours[0],
|
||||
color: data.color || colours[1],
|
||||
slug: slug,
|
||||
@@ -49,6 +49,7 @@ module.exports = function(Categories) {
|
||||
|
||||
async.series([
|
||||
async.apply(db.setObject, 'category:' + category.cid, category),
|
||||
async.apply(Categories.parseDescription, category.cid, category.description),
|
||||
async.apply(db.sortedSetAdd, 'categories:cid', category.order, category.cid),
|
||||
async.apply(db.sortedSetAdd, 'cid:' + parentCid + ':children', category.order, category.cid),
|
||||
async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'administrators'),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
var async = require('async'),
|
||||
db = require('../database'),
|
||||
utils = require('../../public/src/utils'),
|
||||
translator = require('../../public/src/modules/translator'),
|
||||
plugins = require('../plugins');
|
||||
|
||||
module.exports = function(Categories) {
|
||||
@@ -27,7 +28,9 @@ module.exports = function(Categories) {
|
||||
|
||||
|
||||
if (modifiedFields.hasOwnProperty('name')) {
|
||||
modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name);
|
||||
translator.translate(modifiedFields.name, function(translated) {
|
||||
modifiedFields.slug = cid + '/' + utils.slugify(translated);
|
||||
});
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) {
|
||||
@@ -69,7 +72,7 @@ module.exports = function(Categories) {
|
||||
if (key === 'order') {
|
||||
updateOrder(cid, value, callback);
|
||||
} else if (key === 'description') {
|
||||
parseDescription(cid, value, callback);
|
||||
Categories.parseDescription(cid, value, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -97,7 +100,7 @@ module.exports = function(Categories) {
|
||||
function (next) {
|
||||
db.setObjectField('category:' + cid, 'parentCid', newParent, next);
|
||||
}
|
||||
], function(err, results) {
|
||||
], function(err) {
|
||||
callback(err);
|
||||
});
|
||||
});
|
||||
@@ -121,13 +124,13 @@ module.exports = function(Categories) {
|
||||
});
|
||||
}
|
||||
|
||||
function parseDescription(cid, description, callback) {
|
||||
Categories.parseDescription = function(cid, description, callback) {
|
||||
plugins.fireHook('filter:parse.raw', description, function(err, parsedDescription) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, callback);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ settingsController.get = function(req, res, callback) {
|
||||
getHomePageRoutes(next);
|
||||
},
|
||||
ips: function (next) {
|
||||
user.getIPs(req.uid, 4, next);
|
||||
user.getIPs(userData.uid, 4, next);
|
||||
},
|
||||
sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID)
|
||||
}, next);
|
||||
|
||||
@@ -5,6 +5,7 @@ var adminController = {
|
||||
categories: require('./admin/categories'),
|
||||
tags: require('./admin/tags'),
|
||||
flags: require('./admin/flags'),
|
||||
blacklist: require('./admin/blacklist'),
|
||||
groups: require('./admin/groups'),
|
||||
appearance: require('./admin/appearance'),
|
||||
extend: {
|
||||
|
||||
16
src/controllers/admin/blacklist.js
Normal file
16
src/controllers/admin/blacklist.js
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
var meta = require('../../meta');
|
||||
|
||||
var blacklistController = {};
|
||||
|
||||
blacklistController.get = function(req, res, next) {
|
||||
meta.blacklist.get(function(err, rules) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.render('admin/manage/ip-blacklist', {rules: rules, title: 'IP Blacklist'});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = blacklistController;
|
||||
@@ -51,7 +51,7 @@ categoryController.get = function(req, res, callback) {
|
||||
return helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
if ((!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) {
|
||||
if (!res.locals.isAPI && (!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) {
|
||||
return helpers.redirect(res, '/category/' + encodeURI(results.categoryData.slug));
|
||||
}
|
||||
|
||||
|
||||
29
src/controllers/globalmods.js
Normal file
29
src/controllers/globalmods.js
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
var user = require('../user');
|
||||
var adminFlagsController = require('./admin/flags');
|
||||
var adminBlacklistController = require('./admin/blacklist');
|
||||
|
||||
var globalModsController = {};
|
||||
|
||||
globalModsController.flagged = function(req, res, next) {
|
||||
user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
|
||||
if (err || !isAdminOrGlobalMod) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
adminFlagsController.get(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
globalModsController.ipBlacklist = function(req, res, next) {
|
||||
user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
|
||||
if (err || !isAdminOrGlobalMod) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
adminBlacklistController.get(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = globalModsController;
|
||||
@@ -12,7 +12,6 @@ var helpers = require('./helpers');
|
||||
|
||||
var Controllers = {
|
||||
topics: require('./topics'),
|
||||
posts: require('./posts'),
|
||||
categories: require('./categories'),
|
||||
category: require('./category'),
|
||||
unread: require('./unread'),
|
||||
@@ -25,7 +24,8 @@ var Controllers = {
|
||||
accounts: require('./accounts'),
|
||||
authentication: require('./authentication'),
|
||||
api: require('./api'),
|
||||
admin: require('./admin')
|
||||
admin: require('./admin'),
|
||||
globalMods: require('./globalmods')
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var user = require('../user');
|
||||
var adminFlagsController = require('./admin/flags');
|
||||
|
||||
var postsController = {};
|
||||
|
||||
postsController.flagged = function(req, res, next) {
|
||||
user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) {
|
||||
if (err || !isAdminOrGlobalMod) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
adminFlagsController.get(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = postsController;
|
||||
@@ -4,7 +4,6 @@
|
||||
var async = require('async');
|
||||
var S = require('string');
|
||||
var nconf = require('nconf');
|
||||
var validator = require('validator');
|
||||
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
@@ -24,6 +23,7 @@ topicsController.get = function(req, res, callback) {
|
||||
var currentPage = parseInt(req.query.page, 10) || 1;
|
||||
var pageCount = 1;
|
||||
var userPrivileges;
|
||||
var settings;
|
||||
|
||||
if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
|
||||
return callback();
|
||||
@@ -54,7 +54,7 @@ topicsController.get = function(req, res, callback) {
|
||||
return helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
if ((!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
|
||||
if (!res.locals.isAPI && (!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) {
|
||||
var url = '/topic/' + encodeURI(results.topic.slug);
|
||||
if (req.params.post_index){
|
||||
url += '/'+req.params.post_index;
|
||||
@@ -62,7 +62,7 @@ topicsController.get = function(req, res, callback) {
|
||||
return helpers.redirect(res, url);
|
||||
}
|
||||
|
||||
var settings = results.settings;
|
||||
settings = results.settings;
|
||||
var postCount = parseInt(results.topic.postcount, 10);
|
||||
pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage));
|
||||
|
||||
@@ -261,6 +261,7 @@ topicsController.get = function(req, res, callback) {
|
||||
data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
|
||||
data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
|
||||
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
|
||||
data.scrollToMyPost = settings.scrollToMyPost;
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
|
||||
data.pagination = pagination.create(currentPage, pageCount);
|
||||
data.pagination.rel.forEach(function(rel) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var winston = require('winston');
|
||||
|
||||
module.exports = function(db, module) {
|
||||
var helpers = module.helpers.mongo;
|
||||
|
||||
@@ -121,9 +119,8 @@ module.exports = function(db, module) {
|
||||
}
|
||||
|
||||
var map = helpers.toMap(items);
|
||||
var returnData = [],
|
||||
index = 0,
|
||||
item;
|
||||
var returnData = [];
|
||||
var item;
|
||||
|
||||
for (var i=0; i<keys.length; ++i) {
|
||||
item = map[keys[i]] || {};
|
||||
@@ -219,7 +216,7 @@ module.exports = function(db, module) {
|
||||
data[field] = '';
|
||||
});
|
||||
|
||||
db.collection('objects').update({_key: key}, {$unset : data}, function(err, res) {
|
||||
db.collection('objects').update({_key: key}, {$unset : data}, function(err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = function(db, module) {
|
||||
bulk.find({_key: key, value: values[i]}).upsert().updateOne({$set: {score: parseInt(scores[i], 10)}});
|
||||
}
|
||||
|
||||
bulk.execute(function(err, result) {
|
||||
bulk.execute(function(err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
@@ -55,7 +55,7 @@ module.exports = function(db, module) {
|
||||
bulk.find({_key: keys[i], value: value}).upsert().updateOne({$set: {score: parseInt(score, 10)}});
|
||||
}
|
||||
|
||||
bulk.execute(function(err, result) {
|
||||
bulk.execute(function(err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
@@ -85,7 +85,7 @@ module.exports = function(db, module) {
|
||||
}
|
||||
value = helpers.valueToString(value);
|
||||
|
||||
db.collection('objects').remove({_key: {$in: keys}, value: value}, function(err, res) {
|
||||
db.collection('objects').remove({_key: {$in: keys}, value: value}, function(err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
@@ -95,16 +95,17 @@ module.exports = function(db, module) {
|
||||
if (!Array.isArray(keys) || !keys.length) {
|
||||
return callback();
|
||||
}
|
||||
var query = {_key: {$in: keys}};
|
||||
|
||||
var scoreQuery = {};
|
||||
if (min !== '-inf') {
|
||||
scoreQuery.$gte = min;
|
||||
query.score = {$gte: min};
|
||||
}
|
||||
if (max !== '+inf') {
|
||||
scoreQuery.$lte = max;
|
||||
query.score = query.score || {};
|
||||
query.score.$lte = max;
|
||||
}
|
||||
|
||||
db.collection('objects').remove({_key: {$in: keys}, score: scoreQuery}, function(err) {
|
||||
db.collection('objects').remove(query, function(err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
@@ -182,12 +183,14 @@ module.exports = function(db, module) {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
var scoreQuery = {};
|
||||
var query = {_key: key};
|
||||
|
||||
if (min !== '-inf') {
|
||||
scoreQuery.$gte = min;
|
||||
query.score = {$gte: min};
|
||||
}
|
||||
if (max !== '+inf') {
|
||||
scoreQuery.$lte = max;
|
||||
query.score = query.score || {};
|
||||
query.score.$lte = max;
|
||||
}
|
||||
|
||||
var fields = {_id: 0, value: 1};
|
||||
@@ -195,7 +198,7 @@ module.exports = function(db, module) {
|
||||
fields.score = 1;
|
||||
}
|
||||
|
||||
db.collection('objects').find({_key:key, score: scoreQuery}, {fields: fields})
|
||||
db.collection('objects').find(query, {fields: fields})
|
||||
.limit(count)
|
||||
.skip(start)
|
||||
.sort({score: sort})
|
||||
|
||||
@@ -26,6 +26,7 @@ var async = require('async'),
|
||||
require('./meta/tags')(Meta);
|
||||
require('./meta/dependencies')(Meta);
|
||||
Meta.templates = require('./meta/templates');
|
||||
Meta.blacklist = require('./meta/blacklist');
|
||||
|
||||
/* Assorted */
|
||||
Meta.userOrGroupExists = function(slug, callback) {
|
||||
|
||||
126
src/meta/blacklist.js
Normal file
126
src/meta/blacklist.js
Normal file
@@ -0,0 +1,126 @@
|
||||
'use strict';
|
||||
|
||||
var ip = require('ip');
|
||||
var winston = require('winston');
|
||||
var async = require('async');
|
||||
var db = require('../database');
|
||||
|
||||
var Blacklist = {
|
||||
_rules: []
|
||||
};
|
||||
|
||||
Blacklist.load = function(callback) {
|
||||
async.waterfall([
|
||||
async.apply(db.get, 'ip-blacklist-rules'),
|
||||
async.apply(Blacklist.validate)
|
||||
], function(err, rules) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
winston.verbose('[meta/blacklist] Loading ' + rules.valid.length + ' blacklist rules');
|
||||
if (rules.invalid.length) {
|
||||
winston.warn('[meta/blacklist] ' + rules.invalid.length + ' invalid blacklist rule(s) were ignored.');
|
||||
}
|
||||
|
||||
Blacklist._rules = {
|
||||
ipv4: rules.ipv4,
|
||||
ipv6: rules.ipv6,
|
||||
cidr: rules.cidr
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
Blacklist.save = function(rules, callback) {
|
||||
db.set('ip-blacklist-rules', rules, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
Blacklist.load(callback);
|
||||
});
|
||||
};
|
||||
|
||||
Blacklist.get = function(callback) {
|
||||
db.get('ip-blacklist-rules', callback);
|
||||
};
|
||||
|
||||
Blacklist.test = function(clientIp, callback) {
|
||||
if (
|
||||
Blacklist._rules.ipv4.indexOf(clientIp) === -1 // not explicitly specified in ipv4 list
|
||||
&& Blacklist._rules.ipv6.indexOf(clientIp) === -1 // not explicitly specified in ipv6 list
|
||||
&& !Blacklist._rules.cidr.some(function(subnet) {
|
||||
return ip.cidrSubnet(subnet).contains(clientIp);
|
||||
}) // not in a blacklisted cidr range
|
||||
) {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
var err = new Error('[[error:blacklisted-ip]]');
|
||||
err.code = 'blacklisted-ip';
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(err);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blacklist.validate = function(rules, callback) {
|
||||
rules = (rules || '').split('\n');
|
||||
var ipv4 = [];
|
||||
var ipv6 = [];
|
||||
var cidr = [];
|
||||
var invalid = [];
|
||||
|
||||
var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/,
|
||||
inlineCommentMatch = /#.*$/,
|
||||
whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1'];
|
||||
|
||||
// Filter out blank lines and lines starting with the hash character (comments)
|
||||
// Also trim inputs and remove inline comments
|
||||
rules = rules.map(function(rule) {
|
||||
rule = rule.replace(inlineCommentMatch, '').trim();
|
||||
return rule.length && !rule.startsWith('#') ? rule : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
// Filter out invalid rules
|
||||
rules = rules.filter(function(rule) {
|
||||
if (whitelist.indexOf(rule) !== -1) {
|
||||
invalid.push(rule);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ip.isV4Format(rule)) {
|
||||
ipv4.push(rule);
|
||||
return true;
|
||||
} else if (ip.isV6Format(rule)) {
|
||||
ipv6.push(rule);
|
||||
return true;
|
||||
} else if (isCidrSubnet.test(rule)) {
|
||||
cidr.push(rule);
|
||||
return true;
|
||||
} else {
|
||||
invalid.push(rule);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
callback(null, {
|
||||
numRules: rules.length + invalid.length,
|
||||
ipv4: ipv4,
|
||||
ipv6: ipv6,
|
||||
cidr: cidr,
|
||||
valid: rules,
|
||||
invalid: invalid
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Blacklist;
|
||||
@@ -70,6 +70,7 @@ module.exports = function(Meta) {
|
||||
source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/textcomplete/jquery.textcomplete.css";';
|
||||
source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";';
|
||||
source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/flags.less";';
|
||||
source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/blacklist.less";';
|
||||
source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/generics.less";';
|
||||
source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/mixins.less";';
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ var app,
|
||||
helpers: require('../controllers/helpers')
|
||||
};
|
||||
|
||||
toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 70);
|
||||
toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100);
|
||||
toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500);
|
||||
|
||||
middleware.authenticate = function(req, res, next) {
|
||||
@@ -257,6 +257,13 @@ middleware.busyCheck = function(req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.applyBlacklist = function(req, res, next) {
|
||||
meta.blacklist.test(req.ip, function(err) {
|
||||
console.log('blacklist returned:', err);
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function(webserver) {
|
||||
app = webserver;
|
||||
middleware.admin = require('./admin')(webserver);
|
||||
|
||||
@@ -72,6 +72,7 @@ module.exports = function(middleware) {
|
||||
var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
|
||||
language = req.query.lang || language;
|
||||
translator.translate(str, language, function(translated) {
|
||||
translated = translator.unescape(translated);
|
||||
translated = translated + '<script id="ajaxify-data" type="application/json">' + ajaxifyData + '</script>';
|
||||
fn(err, translated);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
var cache = require('./cache');
|
||||
var plugins = require('../plugins');
|
||||
var translator = require('../../public/src/modules/translator');
|
||||
|
||||
module.exports = function(Posts) {
|
||||
|
||||
@@ -24,6 +25,8 @@ module.exports = function(Posts) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data.postData.content = translator.escape(data.postData.content);
|
||||
|
||||
if (global.env === 'production' && data.postData.pid) {
|
||||
cache.set(data.postData.pid, data.postData.content);
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ function addRoutes(router, middleware, controllers) {
|
||||
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
|
||||
|
||||
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
|
||||
|
||||
router.get('/manage/flags', middlewares, controllers.admin.flags.get);
|
||||
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
|
||||
|
||||
router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate);
|
||||
router.get('/manage/users/search', middlewares, controllers.admin.users.search);
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
}));
|
||||
});
|
||||
|
||||
router.post('/register', Auth.middleware.applyCSRF, controllers.authentication.register);
|
||||
router.post('/login', Auth.middleware.applyCSRF, controllers.authentication.login);
|
||||
router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register);
|
||||
router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
|
||||
router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
|
||||
|
||||
hotswap.replace('auth', router);
|
||||
|
||||
@@ -36,8 +36,9 @@ function mainRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
|
||||
}
|
||||
|
||||
function postRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/posts/flags', middleware, [], controllers.posts.flagged);
|
||||
function globalModRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist);
|
||||
setupPageRoute(app, '/posts/flags', middleware, [], controllers.globalMods.flagged);
|
||||
}
|
||||
|
||||
function topicRoutes(app, middleware, controllers) {
|
||||
@@ -111,7 +112,7 @@ module.exports = function(app, middleware) {
|
||||
|
||||
mainRoutes(router, middleware, controllers);
|
||||
topicRoutes(router, middleware, controllers);
|
||||
postRoutes(router, middleware, controllers);
|
||||
globalModRoutes(router, middleware, controllers);
|
||||
tagRoutes(router, middleware, controllers);
|
||||
categoryRoutes(router, middleware, controllers);
|
||||
accountRoutes(router, middleware, controllers);
|
||||
@@ -187,9 +188,12 @@ function handle404(app, middleware) {
|
||||
|
||||
function handleErrors(app, middleware) {
|
||||
app.use(function(err, req, res, next) {
|
||||
if (err.code === 'EBADCSRFTOKEN') {
|
||||
winston.error(req.path + '\n', err.message);
|
||||
return res.sendStatus(403);
|
||||
switch (err.code) {
|
||||
case 'EBADCSRFTOKEN':
|
||||
winston.error(req.path + '\n', err.message);
|
||||
return res.sendStatus(403);
|
||||
case 'blacklisted-ip':
|
||||
return res.status(403).type('text/plain').send(err.message);
|
||||
}
|
||||
|
||||
if (parseInt(err.status, 10) === 302 && err.path) {
|
||||
|
||||
@@ -85,7 +85,9 @@ function searchInContent(data, callback) {
|
||||
topics.getMainPids(results.tids, next);
|
||||
},
|
||||
function(mainPids, next) {
|
||||
results.pids = mainPids.concat(results.pids).filter(function(pid, index, array) {
|
||||
results.pids = mainPids.concat(results.pids).map(function(pid) {
|
||||
return pid && pid.toString();
|
||||
}).filter(function(pid, index, array) {
|
||||
return pid && array.indexOf(pid) === index;
|
||||
});
|
||||
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
var meta = require('./meta');
|
||||
|
||||
function expandObjBy(obj1, obj2) {
|
||||
var key, val1, val2, changed = false;
|
||||
var key, val1, val2, xorValIsArray, changed = false;
|
||||
for (key in obj2) {
|
||||
if (obj2.hasOwnProperty(key)) {
|
||||
val2 = obj2[key];
|
||||
val1 = obj1[key];
|
||||
if (!obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) {
|
||||
xorValIsArray = Array.isArray(val1) ^ Array.isArray(val2);
|
||||
if (xorValIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) {
|
||||
obj1[key] = val2;
|
||||
changed = true;
|
||||
} else if (typeof val2 === 'object' && expandObjBy(val1, val2)) {
|
||||
changed = true;
|
||||
} else if (typeof val2 === 'object' && !Array.isArray(val2)) {
|
||||
if (expandObjBy(val1, val2)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,10 +29,10 @@ function trim(obj1, obj2) {
|
||||
val1 = obj1[key];
|
||||
if (!obj2.hasOwnProperty(key)) {
|
||||
delete obj1[key];
|
||||
} else if (typeof val1 === 'object') {
|
||||
} else if (typeof val1 === 'object' && !Array.isArray(val1)) {
|
||||
trim(val1, obj2[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,15 +29,16 @@ pubsub.on('sync:stats:end', function(data) {
|
||||
stats[data.id] = data.stats;
|
||||
});
|
||||
|
||||
pubsub.on('sync:stats:guests', function() {
|
||||
var io = require('../index').server;
|
||||
|
||||
var roomClients = io.sockets.adapter.rooms;
|
||||
var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0;
|
||||
pubsub.publish('sync:stats:guests:end', guestCount);
|
||||
});
|
||||
|
||||
SocketRooms.getTotalGuestCount = function(callback) {
|
||||
var count = 0;
|
||||
pubsub.once('sync:stats:guests', function() {
|
||||
var io = require('../index').server;
|
||||
|
||||
var roomClients = io.sockets.adapter.rooms;
|
||||
var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0;
|
||||
pubsub.publish('sync:stats:guests:end', guestCount);
|
||||
});
|
||||
|
||||
pubsub.on('sync:stats:guests:end', function(guestCount) {
|
||||
count += guestCount;
|
||||
|
||||
27
src/socket.io/blacklist.js
Normal file
27
src/socket.io/blacklist.js
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
|
||||
var SocketBlacklist = {};
|
||||
|
||||
SocketBlacklist.validate = function(socket, data, callback) {
|
||||
meta.blacklist.validate(data.rules, callback);
|
||||
};
|
||||
|
||||
SocketBlacklist.save = function(socket, rules, callback) {
|
||||
user.isAdminOrGlobalMod(socket.uid, function(err, isAdminOrGlobalMod) {
|
||||
if (err || !isAdminOrGlobalMod) {
|
||||
return callback(err || new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
meta.blacklist.save(rules, callback);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = SocketBlacklist;
|
||||
@@ -121,7 +121,7 @@ function onMessage(socket, payload) {
|
||||
|
||||
function requireModules() {
|
||||
var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
|
||||
'notifications', 'plugins', 'posts', 'topics', 'user'
|
||||
'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist'
|
||||
];
|
||||
|
||||
modules.forEach(function(module) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
var async = require('async');
|
||||
var user = require('../user');
|
||||
var notifications = require('../notifications');
|
||||
var utils = require('../../public/src/utils');
|
||||
|
||||
var SocketNotifs = {};
|
||||
|
||||
@@ -15,7 +16,7 @@ SocketNotifs.get = function(socket, data, callback) {
|
||||
};
|
||||
|
||||
SocketNotifs.loadMore = function(socket, data, callback) {
|
||||
if (!data || !parseInt(data.after, 10)) {
|
||||
if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
if (!socket.uid) {
|
||||
|
||||
@@ -88,7 +88,7 @@ module.exports = function(SocketTopics) {
|
||||
};
|
||||
|
||||
SocketTopics.loadMoreUnreadTopics = function(socket, data, callback) {
|
||||
if (!data || !data.after) {
|
||||
if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ module.exports = function(SocketTopics) {
|
||||
};
|
||||
|
||||
SocketTopics.loadMoreFromSet = function(socket, data, callback) {
|
||||
if (!data || !data.after || !data.set) {
|
||||
if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ module.exports = function(User) {
|
||||
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
|
||||
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
|
||||
settings.bootswatchSkin = settings.bootswatchSkin || 'default';
|
||||
settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) ===1;
|
||||
|
||||
callback(null, settings);
|
||||
});
|
||||
@@ -120,7 +121,8 @@ module.exports = function(User) {
|
||||
restrictChat: data.restrictChat,
|
||||
topicSearchEnabled: data.topicSearchEnabled,
|
||||
groupTitle: data.groupTitle,
|
||||
homePageRoute: data.homePageCustom || data.homePageRoute
|
||||
homePageRoute: data.homePageCustom || data.homePageRoute,
|
||||
scrollToMyPost: data.scrollToMyPost
|
||||
};
|
||||
|
||||
if (data.bootswatchSkin) {
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
<fieldset>
|
||||
<label for="change-group-desc">Description</label>
|
||||
<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" value="{group.description}" /><br />
|
||||
<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" value="{group.description}" maxlength="255" /><br />
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="change-group-user-title">Title of Members</label>
|
||||
<input type="text" class="form-control" id="change-group-user-title" placeholder="The title of users if they are a member of this group" value="{group.userTitle}"/><br />
|
||||
<input type="text" class="form-control" id="change-group-user-title" placeholder="The title of users if they are a member of this group" value="{group.userTitle}" maxlength="40" /><br />
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
|
||||
40
src/views/admin/manage/ip-blacklist.tpl
Normal file
40
src/views/admin/manage/ip-blacklist.tpl
Normal file
@@ -0,0 +1,40 @@
|
||||
<div class="flags">
|
||||
<div class="col-lg-12">
|
||||
<p class="lead">
|
||||
Configure your IP blacklist here.
|
||||
</p>
|
||||
<p>
|
||||
Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs
|
||||
is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and
|
||||
they will be prevented from logging in to or registering a new account.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<textarea id="blacklist-rules">{rules}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Active Rules</div>
|
||||
<div class="panel-body">
|
||||
<button type="button" class="btn btn-warning" data-action="test"><i class="fa fa-bomb"></i> Validate Blacklist</button>
|
||||
<button type="button" class="btn btn-primary" data-action="apply"><i class="fa fa-save"></i> Apply Blacklist</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Syntax Hints</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Define a single IP addresses per line. You can add IP blocks as long as they follow the CIDR format (e.g.
|
||||
<code>192.168.100.0/22</code>).
|
||||
</p>
|
||||
<p>
|
||||
You can add in comments by starting lines with the <code>#</code> symbol.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
14
src/views/admin/partials/blacklist-validate.tpl
Normal file
14
src/views/admin/partials/blacklist-validate.tpl
Normal file
@@ -0,0 +1,14 @@
|
||||
<p class="lead">
|
||||
<strong>{valid.length}</strong> out of <strong>{numRules}</strong> rule(s) valid.
|
||||
</p>
|
||||
|
||||
<!-- IF invalid.length -->
|
||||
<p>
|
||||
The following <strong>{invalid.length}</strong> rules are invalid:
|
||||
</p>
|
||||
<ul>
|
||||
<!-- BEGIN invalid -->
|
||||
<li><code>@value</code></li>
|
||||
<!-- END invalid -->
|
||||
</ul>
|
||||
<!-- ENDIF invalid.length -->
|
||||
@@ -20,6 +20,7 @@
|
||||
<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -173,6 +174,7 @@
|
||||
<li><a href="{relative_path}/admin/manage/registration">Registration Queue</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">Groups</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/flags">Flags</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">IP Blacklist</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown menu-item">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -95,7 +95,8 @@ function initializeNodeBB(callback) {
|
||||
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'nodebb.min.js'),
|
||||
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, 'acp.min.js'),
|
||||
async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile),
|
||||
async.apply(meta.sounds.init)
|
||||
async.apply(meta.sounds.init),
|
||||
async.apply(meta.blacklist.load)
|
||||
], next);
|
||||
},
|
||||
function(results, next) {
|
||||
|
||||
Reference in New Issue
Block a user