Add modlogs (#48)

* public mod logs, per day and list of log days

* action handler variable names and logic changes, also dont duplicate modloglist code
merge-requests/208/head
Tom 5 years ago committed by GitHub
parent 0a64859358
commit f0ca405236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      controllers/forms/actions.js
  2. 2
      controllers/forms/deleteboard.js
  3. 4
      controllers/forms/globalactions.js
  4. 8
      controllers/pages.js
  5. 2
      db/bans.js
  6. 3
      db/index.js
  7. 48
      db/modlogs.js
  8. 8
      gulp/res/css/style.css
  9. 51
      helpers/build.js
  10. 3
      helpers/captcha/captchaverify.js
  11. 22
      helpers/checks/actionchecker.js
  12. 2
      helpers/checks/bancheck.js
  13. 2
      helpers/checks/spamcheck.js
  14. 12
      helpers/datearray.js
  15. 8
      helpers/paramconverter.js
  16. 112
      models/forms/actionhandler.js
  17. 13
      models/forms/deleteboard.js
  18. 2
      models/forms/removebans.js
  19. 30
      models/pages/modlog.js
  20. 18
      models/pages/modloglist.js
  21. 30
      views/includes/actionfooter.pug
  22. 14
      views/includes/actionfooter_globalmanage.pug
  23. 20
      views/includes/actionfooter_manage.pug
  24. 2
      views/includes/bannersheader.pug
  25. 2
      views/includes/boardheader.pug
  26. 2
      views/includes/catalogheader.pug
  27. 4
      views/includes/logheader.pug
  28. 2
      views/includes/pages.pug
  29. 2
      views/mixins/catalogtile.pug
  30. 4
      views/pages/banners.pug
  31. 6
      views/pages/board.pug
  32. 4
      views/pages/catalog.pug
  33. 40
      views/pages/modlog.pug
  34. 40
      views/pages/modloglist.pug
  35. 8
      views/pages/thread.pug

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Posts = require(__dirname+'/../../db/posts.js') const { Posts } = require(__dirname+'/../../db/')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js') , actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js'); , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
@ -16,7 +16,7 @@ module.exports = async (req, res, next) => {
res.locals.actions = actionChecker(req); res.locals.actions = actionChecker(req);
//make sure they selected at least 1 action //make sure they selected at least 1 action
if (!res.locals.actions.anyValid) { if (res.locals.actions.validActions.length === 0) {
errors.push('No actions selected'); errors.push('No actions selected');
} }

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Boards = require(__dirname+'/../../db/boards.js') const { Boards } = require(__dirname+'/../../db/')
, deleteBoard = require(__dirname+'/../../models/forms/deleteboard.js') , deleteBoard = require(__dirname+'/../../models/forms/deleteboard.js')
, boardUriRegex = require(__dirname+'/../../helpers/checks/boarduriregex.js') , boardUriRegex = require(__dirname+'/../../helpers/checks/boarduriregex.js')

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Posts = require(__dirname+'/../../db/posts.js') const { Posts } = require(__dirname+'/../../db/')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js') , actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js'); , actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
@ -16,7 +16,7 @@ module.exports = async (req, res, next) => {
res.locals.actions = actionChecker(req); res.locals.actions = actionChecker(req);
//make sure they have any global actions, and that they only selected global actions //make sure they have any global actions, and that they only selected global actions
if (!res.locals.actions.anyGlobal || res.locals.actions.anyValid > res.locals.actions.anyGlobal) { if (es.locals.actions.numGlobal === 0 || res.locals.actions.validActions.length > res.locals.actions.numGlobal) {
errors.push('Invalid actions selected'); errors.push('Invalid actions selected');
} }

@ -24,7 +24,9 @@ const express = require('express')
, news = require(__dirname+'/../models/pages/news.js') , news = require(__dirname+'/../models/pages/news.js')
, captchaPage = require(__dirname+'/../models/pages/captchapage.js') , captchaPage = require(__dirname+'/../models/pages/captchapage.js')
, captcha = require(__dirname+'/../models/pages/captcha.js') , captcha = require(__dirname+'/../models/pages/captcha.js')
, thread = require(__dirname+'/../models/pages/thread.js'); , thread = require(__dirname+'/../models/pages/thread.js')
, modlog = require(__dirname+'/../models/pages/modlog.js')
, modloglist = require(__dirname+'/../models/pages/modloglist.js');
//homepage with board list //homepage with board list
router.get('/index.html', home); router.get('/index.html', home);
@ -38,6 +40,10 @@ router.get('/:board/thread/:id(\\d+).html', Boards.exists, paramConverter, Posts
// board catalog page // board catalog page
router.get('/:board/catalog.html', Boards.exists, catalog); router.get('/:board/catalog.html', Boards.exists, catalog);
// modlogs
router.get('/:board/logs.html', Boards.exists, modloglist);
router.get('/:board/logs/:date(\\d{2}-\\d{2}-\\d{4}).html', Boards.exists, paramConverter, modlog);
// random board banner // random board banner
router.get('/randombanner', randombanner); router.get('/randombanner', randombanner);

@ -48,7 +48,7 @@ module.exports = {
'_id': { '_id': {
'$in': ids '$in': ids
} }
}) });
}, },
deleteBoard: (board) => { deleteBoard: (board) => {

@ -9,6 +9,7 @@ module.exports = {
Captchas: require(__dirname+'/captchas.js'), Captchas: require(__dirname+'/captchas.js'),
Files: require(__dirname+'/files.js'), Files: require(__dirname+'/files.js'),
News: require(__dirname+'/news.js'), News: require(__dirname+'/news.js'),
Ratelimits: require(__dirname+'/ratelimits.js') Ratelimits: require(__dirname+'/ratelimits.js'),
Modlogs: require(__dirname+'/modlogs.js'),
} }

@ -0,0 +1,48 @@
'use strict';
const Mongo = require(__dirname+'/db.js')
, db = Mongo.client.db('jschan').collection('modlog');
module.exports = {
getFirst: (board) => {
return db.find({
'board': board._id
}).sort({_id:-1}).limit(1).toArray();
},
getLast: (board) => {
return db.find({
'board': board._id
}).sort({_id:1}).limit(1).toArray();
},
findBetweenDate: (board, start, end) => {
const startDate = Mongo.ObjectId.createFromTime(Math.floor(start.getTime()/1000));
const endDate = Mongo.ObjectId.createFromTime(Math.floor(end.getTime()/1000));
return db.find({
'_id': {
'$gte': startDate,
'$lte': endDate
},
'board': board._id
}).sort({
'_id': -1
}).toArray();
},
insertMany: (events) => {
return db.insertMany(events);
},
deleteBoard: (board) => {
return db.deleteMany({
'board': board
});
},
deleteAll: () => {
return db.deleteMany({});
},
}

@ -9,8 +9,6 @@
--transparent-darken: #00000010; --transparent-darken: #00000010;
--highlighted-post-color: #d6bad0; --highlighted-post-color: #d6bad0;
--highlighted-post-outline-color: #ba9dbf; --highlighted-post-outline-color: #ba9dbf;
--report-color: #fca;
--report-outline-color: #c97;
--board-title: #af0a0f; --board-title: #af0a0f;
--hr: lightgray; --hr: lightgray;
--font-color: black; --font-color: black;
@ -37,8 +35,6 @@
--transparent-darken: #00000010; --transparent-darken: #00000010;
--highlighted-post-color: #ff000010; --highlighted-post-color: #ff000010;
--highlighted-post-outline-color: #111; --highlighted-post-outline-color: #111;
--report-color: #fca;
--report-outline-color: #c97;
--board-title: #c5c8c6; --board-title: #c5c8c6;
--hr: #282a2e; --hr: #282a2e;
--font-color: #c5c8c6; --font-color: #c5c8c6;
@ -275,8 +271,8 @@ p {
.reports { .reports {
margin-top: 5px; margin-top: 5px;
background: var(--report-color) !important; background: var(--highlighted-post-color) !important;
border-color: var(--report-outline-color)!important; border-color: var(--highlighted-post-outline-color)!important;
border-width: 1px 0; border-width: 1px 0;
border-style: solid none; border-style: solid none;
} }

@ -2,10 +2,8 @@
const Mongo = require(__dirname+'/../db/db.js') const Mongo = require(__dirname+'/../db/db.js')
, msTime = require(__dirname+'/mstime.js') , msTime = require(__dirname+'/mstime.js')
, Posts = require(__dirname+'/../db/posts.js') , dateArray = require(__dirname+'/datearray.js')
, Files = require(__dirname+'/../db/files.js') , { Posts, Files, Boards, News, Modlogs } = require(__dirname+'/../db/')
, Boards = require(__dirname+'/../db/boards.js')
, News = require(__dirname+'/../db/news.js')
, render = require(__dirname+'/render.js'); , render = require(__dirname+'/render.js');
module.exports = { module.exports = {
@ -111,6 +109,51 @@ module.exports = {
console.timeEnd(label); console.timeEnd(label);
}, },
buildModLog: async (board, startDate, endDate, logs) => {
if (!startDate || !endDate) {
startDate = new Date(); //this is being built by action handler so will always be current date
endDate = new Date(startDate.getTime());
startDate.setHours(0,0,0,0);
endDate.setHours(23,59,59,999);
}
const day = ('0'+startDate.getDate()).slice(-2);
const month = ('0'+(startDate.getMonth()+1)).slice(-2);
const year = startDate.getFullYear();
const label = `/${board._id}/logs/${month}-${day}-${year}.html`;
console.time(label);
if (!logs) {
logs = await Modlogs.findBetweenDate(board, startDate, endDate);
}
await render(label, 'modlog.pug', {
board,
logs,
startDate,
endDate
});
console.timeEnd(label);
},
buildModLogList: async (board) => {
const label = `/${board._id}/logs.html`;
console.time(label);
let dates = []
const [ firstLog, lastLog ] = await Promise.all([
Modlogs.getFirst(board),
Modlogs.getLast(board)
]);
if (firstLog.length > 0 && lastLog.length > 0) {
const firstLogDate = firstLog[0].date;
firstLogDate.setHours(1,0,0,0);
const lastLogDate = lastLog[0].date;
dates = dateArray(firstLogDate, lastLogDate);
}
await render(label, 'modloglist.pug', {
board,
dates
});
console.timeEnd(label);
},
buildHomepage: async () => { buildHomepage: async () => {
const label = '/index.html'; const label = '/index.html';
console.time(label); console.time(label);

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const Captchas = require(__dirname+'/../../db/captchas.js') const { Captchas, Ratelimits } = require(__dirname+'/../../db/')
, Ratelimits = require(__dirname+'/../../db/ratelimits.js')
, { ObjectId } = require(__dirname+'/../../db/db.js') , { ObjectId } = require(__dirname+'/../../db/db.js')
, remove = require('fs-extra').remove , remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../files/uploadDirectory.js'); , uploadDirectory = require(__dirname+'/../files/uploadDirectory.js');

@ -13,40 +13,40 @@ const actions = [
{name:'global_report', global:true, auth:4, passwords:false, build:false}, {name:'global_report', global:true, auth:4, passwords:false, build:false},
{name:'delete_ip_board', global:true, auth:3, passwords:false, build:true}, {name:'delete_ip_board', global:true, auth:3, passwords:false, build:true},
{name:'delete_ip_global', global:true, auth:1, passwords:false, build:true}, {name:'delete_ip_global', global:true, auth:1, passwords:false, build:true},
{name:'dismiss', global:false, auth:3, passwords:false, build:false}, {name:'dismiss', global:false, auth:3, passwords:false, build:true},
{name:'global_dismiss', global:true, auth:1, passwords:false, build:false}, {name:'global_dismiss', global:true, auth:1, passwords:false, build:true},
{name:'ban', global:false, auth:3, passwords:false, build:true}, {name:'ban', global:false, auth:3, passwords:false, build:true},
{name:'global_ban', global:true, auth:1, passwords:false, build:true}, {name:'global_ban', global:true, auth:1, passwords:false, build:true},
]; ];
module.exports = (req, res) => { module.exports = (req, res) => {
let anyGlobal = 0 let numGlobal = 0
, authRequired = 4 , authRequired = 4
, anyPasswords = 0 , numPasswords = 0
, anyBuild = 0 , numBuild = 0
, anyValid = 0; , validActions = [];
for (let i = 0; i < actions.length; i++) { for (let i = 0; i < actions.length; i++) {
const action = actions[i]; const action = actions[i];
const bodyHasAction = req.body[action.name]; const bodyHasAction = req.body[action.name];
if (bodyHasAction) { if (bodyHasAction) {
anyValid++; validActions.push(action.name);
if (action.global) { if (action.global) {
anyGlobal++; numGlobal++;
} }
if (action.auth && action.auth < authRequired) { if (action.auth && action.auth < authRequired) {
authRequired = action.auth; authRequired = action.auth;
} }
if (action.passwords) { if (action.passwords) {
anyPasswords++; numPasswords++;
} }
if (action.build) { if (action.build) {
anyBuild++ numBuild++
} }
} }
} }
return { anyGlobal, authRequired, anyValid, anyPasswords, anyBuild }; return { numGlobal, authRequired, validActions, numPasswords, numBuild };
} }

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Bans = require(__dirname+'/../../db/bans.js') const { Bans } = require(__dirname+'/../../db/')
, hasPerms = require(__dirname+'/hasperms.js'); , hasPerms = require(__dirname+'/hasperms.js');
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const Mongo = require(__dirname+'/../../db/db.js') const Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js') , { Posts } = require(__dirname+'/../../db/')
, msTime = require(__dirname+'/../mstime.js') , msTime = require(__dirname+'/../mstime.js')
module.exports = async (req, res) => { module.exports = async (req, res) => {

@ -0,0 +1,12 @@
'use strict';
//https://stackoverflow.com/a/4413721
module.exports = (startDate, stopDate) => {
var dateArray = new Array();
var currentDate = startDate;
while (currentDate <= stopDate) {
dateArray.push(new Date (currentDate.valueOf()));
currentDate.setDate(currentDate.getDate() + 1);
}
return dateArray;
}

@ -92,6 +92,14 @@ module.exports = (req, res, next) => {
if (req.params.id) { if (req.params.id) {
req.params.id = +req.params.id; req.params.id = +req.params.id;
} }
//moglog date
if (req.params.date) {
const dateString = req.params.date.replace(/-/g, '/');
const date = new Date(dateString);
if (date !== 'Invalid Date') {
res.locals.date = date;
}
}
//board page //board page
if (req.params.page) { if (req.params.page) {
req.params.page = req.params.page === 'index' ? 'index' : +req.params.page; req.params.page = req.params.page === 'index' ? 'index' : +req.params.page;

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { Posts, Boards } = require(__dirname+'/../../db/') const { Posts, Boards, Modlogs } = require(__dirname+'/../../db/')
, Mongo = require(__dirname+'/../../db/db.js') , Mongo = require(__dirname+'/../../db/db.js')
, banPoster = require(__dirname+'/banposter.js') , banPoster = require(__dirname+'/banposter.js')
, deletePosts = require(__dirname+'/deletepost.js') , deletePosts = require(__dirname+'/deletepost.js')
@ -16,7 +16,7 @@ const { Posts, Boards } = require(__dirname+'/../../db/')
, dismissGlobalReports = require(__dirname+'/dismissglobalreport.js') , dismissGlobalReports = require(__dirname+'/dismissglobalreport.js')
, { remove } = require('fs-extra') , { remove } = require('fs-extra')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js') , uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, { buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../helpers/build.js') , { buildModLog, buildModLogList, buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../helpers/build.js')
, { postPasswordSecret } = require(__dirname+'/../../configs/main.json') , { postPasswordSecret } = require(__dirname+'/../../configs/main.json')
, { createHash, timingSafeEqual } = require('crypto'); , { createHash, timingSafeEqual } = require('crypto');
@ -26,7 +26,7 @@ module.exports = async (req, res, next) => {
const postMongoIds = res.locals.posts.map(post => Mongo.ObjectId(post._id)); const postMongoIds = res.locals.posts.map(post => Mongo.ObjectId(post._id));
let passwordPostMongoIds = []; let passwordPostMongoIds = [];
let passwordPosts = []; let passwordPosts = [];
if (res.locals.authLevel >= 4 && res.locals.actions.anyPasswords) { if (res.locals.permLevel >= 4 && res.locals.actions.numPasswords > 0) {
if (req.body.password && req.body.password.length > 0) { if (req.body.password && req.body.password.length > 0) {
//hash their input and make it a buffer //hash their input and make it a buffer
const inputPasswordHash = createHash('sha256').update(postPasswordSecret + req.body.password).digest('base64'); const inputPasswordHash = createHash('sha256').update(postPasswordSecret + req.body.password).digest('base64');
@ -34,7 +34,7 @@ module.exports = async (req, res, next) => {
passwordPosts = res.locals.posts.filter(post => { passwordPosts = res.locals.posts.filter(post => {
if (post.password != null) { if (post.password != null) {
const postBuffer = Buffer.from(post.password); const postBuffer = Buffer.from(post.password);
if (timingSafeEqual(inputBuffer, postBuffer) === true) { if (timingSafeEqual(inputPasswordBuffer, postBuffer) === true) {
passwordPostMongoIds.push(Mongo.ObjectId(post._id)); passwordPostMongoIds.push(Mongo.ObjectId(post._id));
return true; return true;
} }
@ -83,20 +83,16 @@ module.exports = async (req, res, next) => {
} }
const messages = []; const messages = [];
const modlogActions = []
const combinedQuery = {}; const combinedQuery = {};
const passwordCombinedQuery = {}; const passwordCombinedQuery = {};
let aggregateNeeded = false; let aggregateNeeded = false;
try { try {
// if getting global banned, board ban doesnt matter // if getting global banned, board ban doesnt matter
if (req.body.global_ban) { if (req.body.ban || req.body.global_ban) {
const { message, action, query } = await banPoster(req, res, next);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
} else if (req.body.ban) {
const { message, action, query } = await banPoster(req, res, next); const { message, action, query } = await banPoster(req, res, next);
if (action) { if (action) {
modlogActions.push(req.body.ban || req.body.global_ban);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
@ -122,6 +118,7 @@ module.exports = async (req, res, next) => {
const { action, message } = await deletePosts(passwordPosts, req.body.delete_ip_global ? null : req.params.board); const { action, message } = await deletePosts(passwordPosts, req.body.delete_ip_global ? null : req.params.board);
messages.push(message); messages.push(message);
if (action) { if (action) {
modlogActions.push(req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global);
aggregateNeeded = true; aggregateNeeded = true;
} }
} else { } else {
@ -129,6 +126,7 @@ module.exports = async (req, res, next) => {
if (req.body.unlink_file || req.body.delete_file) { if (req.body.unlink_file || req.body.delete_file) {
const { message, action, query } = await deletePostsFiles(passwordPosts, req.body.unlink_file); const { message, action, query } = await deletePostsFiles(passwordPosts, req.body.unlink_file);
if (action) { if (action) {
modlogActions.push(req.body.unlink_file || req.body.delete_file);
aggregateNeeded = true; aggregateNeeded = true;
passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query} passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query}
} }
@ -136,6 +134,7 @@ module.exports = async (req, res, next) => {
} else if (req.body.spoiler) { } else if (req.body.spoiler) {
const { message, action, query } = spoilerPosts(passwordPosts); const { message, action, query } = spoilerPosts(passwordPosts);
if (action) { if (action) {
modlogActions.push(req.body.spoiler);
passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query} passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
@ -144,6 +143,7 @@ module.exports = async (req, res, next) => {
if (req.body.sage) { if (req.body.sage) {
const { message, action, query } = sagePosts(res.locals.posts); const { message, action, query } = sagePosts(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.sage);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
@ -151,22 +151,25 @@ module.exports = async (req, res, next) => {
if (req.body.lock) { if (req.body.lock) {
const { message, action, query } = lockPosts(res.locals.posts); const { message, action, query } = lockPosts(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.lock);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} }
if (req.body.sticky) { if (req.body.sticky) {
const { message, action, query } = stickyPosts(res.locals.posts); const { message, action, query } = stickyPosts(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.sticky);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} }
if (req.body.cyclic) { if (req.body.cyclic) {
const { message, action, query } = cyclePosts(res.locals.posts); const { message, action, query } = cyclePosts(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.cyclic);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} }
// cannot report and dismiss at same time // cannot report and dismiss at same time
@ -174,13 +177,14 @@ module.exports = async (req, res, next) => {
const { message, action, query } = reportPosts(req, res, res.locals.posts); const { message, action, query } = reportPosts(req, res, res.locals.posts);
if (action) { if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} else if (req.body.dismiss) { } else if (req.body.dismiss) {
const { message, action, query } = dismissReports(res.locals.posts); const { message, action, query } = dismissReports(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.dismiss);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} }
// cannot report and dismiss at same time // cannot report and dismiss at same time
@ -193,15 +197,15 @@ module.exports = async (req, res, next) => {
} else if (req.body.global_dismiss) { } else if (req.body.global_dismiss) {
const { message, action, query } = dismissGlobalReports(res.locals.posts); const { message, action, query } = dismissGlobalReports(res.locals.posts);
if (action) { if (action) {
modlogActions.push(req.body.global_dismiss);
combinedQuery[action] = { ...combinedQuery[action], ...query} combinedQuery[action] = { ...combinedQuery[action], ...query}
} }
messages.push(message); messages.push(message);
} }
} }
//console.log(require('util').inspect(combinedQuery, {depth: null})) const actionBulkWrites = [];
const bulkWrites = [];
if (Object.keys(combinedQuery).length > 0) { if (Object.keys(combinedQuery).length > 0) {
bulkWrites.push({ actionBulkWrites.push({
'updateMany': { 'updateMany': {
'filter': { 'filter': {
'_id': { '_id': {
@ -213,7 +217,7 @@ module.exports = async (req, res, next) => {
}); });
} }
if (Object.keys(passwordCombinedQuery).length > 0) { if (Object.keys(passwordCombinedQuery).length > 0) {
bulkWrites.push({ actionBulkWrites.push({
'updateMany': { 'updateMany': {
'filter': { 'filter': {
'_id': { '_id': {
@ -224,14 +228,48 @@ module.exports = async (req, res, next) => {
} }
}); });
} }
//execute actions now //execute actions now
if (bulkWrites.length > 0) { if (actionBulkWrites.length > 0) {
await Posts.db.bulkWrite(bulkWrites); await Posts.db.bulkWrite(actionBulkWrites);
} }
//if there are actions that can cause some rebuilding //if there are actions that can cause some rebuilding
if (res.locals.actions.anyBuild > 0) { if (res.locals.actions.numBuild > 0) {
//modlog
if (modlogActions.length > 0) {
const modlog = {};
const logDate = new Date(); //all events current date
for (let i = 0; i < passwordPosts.length; i++) {
const post = passwordPosts[i];
if (!modlog[post.board]) {
//per board actions, all actions combined to one event
const logUser = res.locals.permLevel < 4 ? req.session.user.username : 'Unregistered User'
modlog[post.board] = {
postIds: [],
actions: modlogActions,
date: logDate,
user: logUser
};
}
//push each post id
modlog[post.board].postIds.push(post.postId);
}
const modlogDocuments = [];
for (let i = 0; i < threadBoards.length; i++) {
const boardName = threadBoards[i];
const boardLog = modlog[boardName];
//make it into documents for the db
modlogDocuments.push({
...boardLog,
'board': boardName
});
}
if (modlogDocuments.length > 0) {
//insert the modlog docs
await Modlogs.insertMany(modlogDocuments);
}
}
//recalculate replies and image counts //recalculate replies and image counts
if (aggregateNeeded) { if (aggregateNeeded) {
@ -263,6 +301,7 @@ module.exports = async (req, res, next) => {
}) })
} }
//fetch threads per board that we only checked posts for //fetch threads per board that we only checked posts for
let threadsEachBoard = await Posts.db.find({ let threadsEachBoard = await Posts.db.find({
'thread': null, 'thread': null,
@ -302,9 +341,10 @@ module.exports = async (req, res, next) => {
for (let i = 0; i < boardNames.length; i++) { for (let i = 0; i < boardNames.length; i++) {
const boardName = boardNames[i]; const boardName = boardNames[i];
const bounds = threadBounds[boardName]; const bounds = threadBounds[boardName];
const board = buildBoards[boardName];
//rebuild impacted threads //rebuild impacted threads
for (let j = 0; j < boardThreadMap[boardName].threads.length; j++) { for (let j = 0; j < boardThreadMap[boardName].threads.length; j++) {
parallelPromises.push(buildThread(boardThreadMap[boardName].threads[j], buildBoards[boardName])); parallelPromises.push(buildThread(boardThreadMap[boardName].threads[j], board));
} }
//refersh any pages affected //refersh any pages affected
const afterPages = Math.ceil((await Posts.getPages(boardName)) / 10); const afterPages = Math.ceil((await Posts.getPages(boardName)) / 10);
@ -318,7 +358,7 @@ module.exports = async (req, res, next) => {
parallelPromises.push(remove(`${uploadDirectory}html/${boardName}/${k}.html`)); parallelPromises.push(remove(`${uploadDirectory}html/${boardName}/${k}.html`));
} }
} }
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, afterPages)); parallelPromises.push(buildBoardMultiple(board, 1, afterPages));
} else { } else {
//number of pages did not change, only possibly building existing pages //number of pages did not change, only possibly building existing pages
const threadPageOldest = await Posts.getThreadPage(boardName, bounds.oldest); const threadPageOldest = await Posts.getThreadPage(boardName, bounds.oldest);
@ -326,18 +366,18 @@ module.exports = async (req, res, next) => {
if (req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global) { if (req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global) {
if (!boardThreadMap[boardName].directThreads) { if (!boardThreadMap[boardName].directThreads) {
//onyl deleting posts from threads, so thread order wont change, thus we dont delete all pages after //onyl deleting posts from threads, so thread order wont change, thus we dont delete all pages after
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest)); parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
} else { } else {
//deleting threads, so we delete all pages after //deleting threads, so we delete all pages after
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, afterPages)); parallelPromises.push(buildBoardMultiple(board, threadPageNewest, afterPages));
} }
} else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant } else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant
//rebuild current and newer pages //rebuild current and newer pages
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest)); parallelPromises.push(buildBoardMultiple(board, 1, threadPageOldest));
} else if (req.body.lock || req.body.sage || req.body.cyclic || req.body.unlink_file) { } else if (req.body.lock || req.body.sage || req.body.cyclic || req.body.unlink_file) {
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest)); parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
} else if (req.body.spoiler || req.body.ban || req.body.global_ban) { } else if (req.body.spoiler || req.body.ban || req.body.global_ban) {
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest)); parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
if (!boardThreadMap[boardName].directThreads) { if (!boardThreadMap[boardName].directThreads) {
catalogRebuild = false; catalogRebuild = false;
//these actions dont affect the catalog tile since not on an OP and dont change reply/image counts //these actions dont affect the catalog tile since not on an OP and dont change reply/image counts
@ -346,7 +386,13 @@ module.exports = async (req, res, next) => {
} }
if (catalogRebuild) { if (catalogRebuild) {
//the actions will affect the catalog, so we better rebuild it //the actions will affect the catalog, so we better rebuild it
parallelPromises.push(buildCatalog(buildBoards[boardName])); parallelPromises.push(buildCatalog(board));
}
if (modlogActions.length > 0) {
//modlog had something added
parallelPromises.push(buildModLog(board));
parallelPromises.push(buildModLogList(board));
//todo: build loglist on a schedule. not necessary to build every time modlog changes, only daily/end of day
} }
} }
await Promise.all(parallelPromises); await Promise.all(parallelPromises);

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { Boards, Posts, Bans } = require(__dirname+'/../../db/') const { Boards, Posts, Bans, Modlogs } = require(__dirname+'/../../db/')
, deletePosts = require(__dirname+'/deletepost.js') , deletePosts = require(__dirname+'/deletepost.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js') , uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, { remove } = require('fs-extra'); , { remove } = require('fs-extra');
@ -15,11 +15,10 @@ module.exports = async (uri) => {
//delete posts and decrement images //delete posts and decrement images
await deletePosts(allPosts, uri, true); await deletePosts(allPosts, uri, true);
} }
//bans for the board are pointless now await Promise.all([
await Bans.deleteBoard(uri); Modlogs.deleteBoard(uri), //bans for the board
//remove all pages for the board Bans.deleteBoard(uri), //bans for the board
await remove(`${uploadDirectory}html/${uri}/`) remove(`${uploadDirectory}html/${uri}/`) //html
]);
//todo: maybe put some of this in parallel
} }

@ -4,6 +4,6 @@ const { Bans } = require(__dirname+'/../../db/');
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
return Bans.removeMany(req.params.board, banIds).then(result => result.deletedCount); return Bans.removeMany(req.params.board, req.body.checkedbans).then(result => result.deletedCount);
} }

@ -0,0 +1,30 @@
'use strict';
const { Posts, Modlogs } = require(__dirname+'/../../db/')
, { buildModLog } = require(__dirname+'/../../helpers/build.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js');
module.exports = async (req, res, next) => {
if (!res.locals.date) {
return next();
}
const startDate = res.locals.date;
const endDate = new Date(startDate.getTime());
startDate.setHours(0,0,0,0);
endDate.setHours(23,59,59,999);
try {
const logs = await Modlogs.findBetweenDate(res.locals.board, startDate, endDate);
if (!logs || logs.length === 0) {
return next();
}
await buildModLog(res.locals.board, startDate, endDate, logs);
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html${req.path}`);
}

@ -0,0 +1,18 @@
'use strict';
const { Posts, Modlogs } = require(__dirname+'/../../db/')
, { buildModLogList } = require(__dirname+'/../../helpers/build.js')
, dateArray = require(__dirname+'/../../helpers/datearray.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js');
module.exports = async (req, res, next) => {
try {
await buildModLogList(res.locals.board);
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html${req.path}`);
}

@ -3,59 +3,59 @@ details.toggle-label
.actions .actions
h4.no-m-p Actions: h4.no-m-p Actions:
label label
input.post-check(type='checkbox', name='delete' value=1) input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts | Delete Posts
label label
input.post-check(type='checkbox', name='unlink_file' value=1) input.post-check(type='checkbox', name='unlink_file' value='Unlink files')
| Unlink Files | Unlink Files
label label
input.post-check(type='checkbox', name='spoiler' value=1) input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files | Spoiler Files
label label
input#password(type='text', name='password', placeholder='post password' autocomplete='off') input#password(type='text', name='password', placeholder='post password' autocomplete='off')
label label
input.post-check(type='checkbox', name='report' value=1) input.post-check(type='checkbox', name='report' value='Report')
| Report | Report
label label
input.post-check(type='checkbox', name='global_report' value=1) input.post-check(type='checkbox', name='global_report' value='Report global')
| Global Report | Global Report
label label
input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off') input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
details.actions details.actions
summary(style='font-weight: bold') Staff Actions: summary(style='font-weight: bold') Staff Actions:
label label
input.post-check(type='checkbox', name='delete_ip_board' value=1) input.post-check(type='checkbox', name='delete_ip_board' value='Delete posts by IP')
| Delete from IP on board | Delete from IP on board
label label
input.post-check(type='checkbox', name='delete_ip_global' value=1) input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally | Delete from IP globally
label label
input.post-check(type='checkbox', name='delete_file' value=1) input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files | Delete Files
label label
input.post-check(type='checkbox', name='ban' value=1) input.post-check(type='checkbox', name='ban' value='Ban')
| Ban Poster | Ban Poster
label label
input.post-check(type='checkbox', name='global_ban' value=1) input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster | Global Ban Poster
label label
input.post-check(type='checkbox', name='preserve_post' value=1) input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban | Show Post In Ban
label label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off') input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
label label
input(type='text', name='ban_duration', placeholder='ban duration e.g. 7d' autocomplete='off') input(type='text', name='ban_duration', placeholder='ban duration e.g. 7d' autocomplete='off')
label label
input.post-check(type='checkbox', name='sticky' value=1) input.post-check(type='checkbox', name='sticky' value='Sticky')
| Toggle Sticky | Toggle Sticky
label label
input.post-check(type='checkbox', name='lock' value=1) input.post-check(type='checkbox', name='lock' value='Lock')
| Toggle Lock | Toggle Lock
label label
input.post-check(type='checkbox', name='sage' value=1) input.post-check(type='checkbox', name='sage' value='Sage')
| Toggle Sage | Toggle Sage
label label
input.post-check(type='checkbox', name='cyclic' value=1) input.post-check(type='checkbox', name='cyclic' value='Cycle')
| Toggle Cycle | Toggle Cycle
.actions .actions
h4.no-m-p Captcha: h4.no-m-p Captcha:

@ -3,25 +3,25 @@ details.toggle-label
.actions .actions
h4.no-m-p Actions: h4.no-m-p Actions:
label label
input.post-check(type='checkbox', name='delete' value=1) input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts | Delete Posts
label label
input.post-check(type='checkbox', name='delete_file' value=1) input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files | Delete Files
label label
input.post-check(type='checkbox', name='spoiler' value=1) input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files | Spoiler Files
label label
input.post-check(type='checkbox', name='delete_ip_global' value=1) input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally | Delete from IP globally
label label
input.post-check(type='checkbox', name='global_dismiss' value=1) input.post-check(type='checkbox', name='global_dismiss' value='Dismiss global reports')
| Dismiss Global Reports | Dismiss Global Reports
label label
input.post-check(type='checkbox', name='global_ban' value=1) input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster | Global Ban Poster
label label
input.post-check(type='checkbox', name='preserve_post' value=1) input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban | Show Post In Ban
label label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off') input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')

@ -3,36 +3,36 @@ details.toggle-label
.actions .actions
h4.no-m-p Actions: h4.no-m-p Actions:
label label
input.post-check(type='checkbox', name='delete' value=1) input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts | Delete Posts
label label
input.post-check(type='checkbox', name='delete_file' value=1) input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files | Delete Files
label label
input.post-check(type='checkbox', name='spoiler' value=1) input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files | Spoiler Files
label label
input.post-check(type='checkbox', name='global_report' value=1) input.post-check(type='checkbox', name='global_report' value='Global report')
| Global Report | Global Report
label label
input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off') input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
label label
input.post-check(type='checkbox', name='delete_ip_board' value=1) input.post-check(type='checkbox', name='delete_ip_board' value='Delete posts by IP')
| Delete from IP on board | Delete from IP on board
label label
input.post-check(type='checkbox', name='delete_ip_global' value=1) input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally | Delete from IP globally
label label
input.post-check(type='checkbox', name='dismiss' value=1) input.post-check(type='checkbox', name='dismiss' value='Dismiss reports')
| Dismiss Reports | Dismiss Reports
label label
input.post-check(type='checkbox', name='ban' value=1) input.post-check(type='checkbox', name='ban' value='Ban')
| Ban Poster | Ban Poster
label label
input.post-check(type='checkbox', name='global_ban' value=1) input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster | Global Ban Poster
label label
input.post-check(type='checkbox', name='preserve_post' value=1) input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban | Show Post In Ban
label label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off') input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')

@ -1,4 +1,4 @@
section.board-header section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100') img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br br
h1.board-title Banners (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/]) h1.board-title Banners (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -1,5 +1,5 @@
section.board-header section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` alt='banner image') img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br br
a.no-decoration(href=`/${board._id}/index.html`) a.no-decoration(href=`/${board._id}/index.html`)
h1.board-title /#{board._id}/ - #{board.settings.name} h1.board-title /#{board._id}/ - #{board.settings.name}

@ -1,4 +1,4 @@
section.board-header section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100') img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br br
h1.board-title Catalog (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/]) h1.board-title Catalog (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -0,0 +1,4 @@
section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br
h1.board-title Logs (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -7,4 +7,4 @@ if maxPage === 0
else else
a(href=`/${board._id}/${i === 1 ? 'index' : i}.html`) #{i} a(href=`/${board._id}/${i === 1 ? 'index' : i}.html`) #{i}
| |
| | | |

@ -3,7 +3,7 @@ mixin catalogtile(board, post, index)
- const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}` - const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}`
header.post-info header.post-info
include ../includes/postmods.pug include ../includes/postmods.pug
a.no-decoration.post-subject(href=postURL) #{post.subject || '#'+post.postId} a.no-decoration.post-subject(href=postURL) #{post.subject || 'No subject'} #{'#'+post.postId}
| - | -
span(title='Replies') R: #{post.replyposts} span(title='Replies') R: #{post.replyposts}
| / | /

@ -13,6 +13,8 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a.bold(href=`/${board._id}/banners.html`) [Banners] a.bold(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1) hr(size=1)
section.catalog section.catalog
if board.banners.length > 0 if board.banners.length > 0
@ -28,3 +30,5 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a.bold(href=`/${board._id}/banners.html`) [Banners] a.bold(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]

@ -13,9 +13,12 @@ block content
include ../includes/stickynav.pug include ../includes/stickynav.pug
nav.pages nav.pages
include ../includes/pages.pug include ../includes/pages.pug
|
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1) hr(size=1)
form(action='/forms/board/'+board._id+'/actions' method='POST' enctype='application/x-www-form-urlencoded') form(action='/forms/board/'+board._id+'/actions' method='POST' enctype='application/x-www-form-urlencoded')
if threads.length === 0 if threads.length === 0
@ -29,7 +32,10 @@ block content
hr(size=1) hr(size=1)
nav.pages nav.pages
include ../includes/pages.pug include ../includes/pages.pug
|
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
include ../includes/actionfooter.pug include ../includes/actionfooter.pug

@ -14,6 +14,8 @@ block content
a.bold(href=`/${board._id}/catalog.html`) [Catalog] a.bold(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1) hr(size=1)
if threads.length === 0 if threads.length === 0
p No posts. p No posts.
@ -27,3 +29,5 @@ block content
a.bold(href=`/${board._id}/catalog.html`) [Catalog] a.bold(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]

@ -0,0 +1,40 @@
extends ../layout.pug
block head
title Logs for #{startDate.toDateString()}
block content
include ../includes/logheader.pug
br
include ../includes/stickynav.pug
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
.table-container.flex-center.mv-10.text-center
table
tr
th Date
th User
th Actions
th Post IDs
for log in logs
tr
td #{log.date.toLocaleString()}
td #{log.user}
td #{log.actions}
td #{log.postIds}
hr(size=1)
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]

@ -0,0 +1,40 @@
extends ../layout.pug
block head
title Log list for /#{board._id}/
block content
include ../includes/logheader.pug
br
include ../includes/stickynav.pug
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
if dates.length === 0
p No Logs.
else
.table-container.flex-center.mv-10.text-center
table
for date in dates
tr
-
const day = ('0'+date.getDate()).slice(-2);
const month = ('0'+(date.getMonth()+1)).slice(-2);
const year = date.getFullYear();
const logName = `${month}-${day}-${year}`;
td: a(href=`logs/${logName}.html`) #{logName}
hr(size=1)
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]

@ -28,6 +28,8 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1) hr(size=1)
form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded') form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded')
section.thread section.thread
@ -37,8 +39,10 @@ block content
hr(size=1) hr(size=1)
nav.pages nav.pages
a(href=`/${board._id}/index.html`) [Index] a(href=`/${board._id}/index.html`) [Index]
| |
a(href=`/${board._id}/catalog.html`) [Catalog] a(href=`/${board._id}/catalog.html`) [Catalog]
| |
a(href=`/${board._id}/banners.html`) [Banners] a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
include ../includes/actionfooter.pug include ../includes/actionfooter.pug

Loading…
Cancel
Save