From 70cbbecec2eb7b7a5619fe0c86c2650d3d39a15f Mon Sep 17 00:00:00 2001 From: Evert Date: Mon, 28 Aug 2017 18:42:16 +0300 Subject: [PATCH] admin panel work a bit --- package-lock.json | 6 ++++ package.json | 1 + server/api/admin.js | 47 +++++++++++++++++++++++++++ server/api/index.js | 18 +++++++++++ server/api/news.js | 21 +----------- server/routes/admin.js | 62 ++++++++++++++++++++++++++++++++--- server/routes/index.js | 5 +-- src/script/admin.js | 71 ++++++++++++++++++++++++++++++++++++++++- src/style/admin.styl | 26 +++++++++++---- views/admin/index.pug | 5 +++ views/admin/layout.pug | 32 ++++++++++++++++++- views/user/password.pug | 2 +- 12 files changed, 257 insertions(+), 39 deletions(-) create mode 100644 server/api/admin.js diff --git a/package-lock.json b/package-lock.json index 69edebe..2b73f9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3585,6 +3585,12 @@ "fd-slicer": "1.0.1" } }, + "mustache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz", + "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=", + "dev": true + }, "mute-stream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", diff --git a/package.json b/package.json index 86c8af4..29af1c8 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "concurrently": "^3.5.0", "eslint-plugin-import": "^2.7.0", "jquery": "^3.2.1", + "mustache": "^2.3.0", "standard": "^10.0.3", "uglify-js": "^1.3.5", "watch": "^1.0.2", diff --git a/server/api/admin.js b/server/api/admin.js new file mode 100644 index 0000000..65ecb56 --- /dev/null +++ b/server/api/admin.js @@ -0,0 +1,47 @@ +import Users from './index' +import Models from './models' + +const perPage = 6 + +function cleanUserObject (dbe) { + return { + id: dbe.id, + username: dbe.username, + display_name: dbe.display_name, + email: dbe.email, + avatar_file: dbe.avatar_file, + activated: dbe.activated === 1, + locked: dbe.locked === 1, + ip_addess: dbe.ip_addess, + password: dbe.password !== null, + nw_privilege: dbe.nw_privilege, + created_at: dbe.created_at + } +} + +const API = { + getAllUsers: async function (page) { + let count = await Models.User.query().count('id as ids') + if (!count.length || !count[0]['ids'] || isNaN(page)) { + return {error: 'No users found'} + } + + count = count[0].ids + let paginated = Users.Pagination(perPage, parseInt(count), page) + let raw = await Models.User.query().offset(paginated.offset).limit(perPage) + + let users = [] + for (let i in raw) { + let entry = raw[i] + + users.push(cleanUserObject(entry)) + } + + return { + page: paginated, + users: users + } + } +} + +module.exports = API diff --git a/server/api/index.js b/server/api/index.js index 03bb21e..37a54c5 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -55,6 +55,24 @@ const API = { Hash: (len) => { return crypto.randomBytes(len).toString('hex') }, + /* ppp - Posts Per Page; dcount - Post Count; page - number of current page */ + Pagination: (ppp, dcount, page) => { + if (!ppp) ppp = 5 + if (!dcount) return null + + let pageCount = Math.ceil(dcount / ppp) + if (page > pageCount) page = pageCount + + let offset = (page - 1) * ppp + + return { + page: page, + perPage: ppp, + pages: pageCount, + offset: offset, + total: dcount + } + }, User: { get: async function (identifier) { let scope = 'id' diff --git a/server/api/news.js b/server/api/news.js index 8856db2..41d2f92 100644 --- a/server/api/news.js +++ b/server/api/news.js @@ -7,25 +7,6 @@ function slugify (title) { return title.toLowerCase().replace(/\W/g, '-').substring(0, 16) } -/* ppp - Posts Per Page; dcount - Post Count; page - number of current page */ -function Pagination (ppp, dcount, page) { - if (!ppp) ppp = 5 - if (!dcount) return null - - let pageCount = Math.ceil(dcount / ppp) - if (page > pageCount) page = pageCount - - let offset = (page - 1) * ppp - - return { - page: page, - perPage: ppp, - pages: pageCount, - offset: offset, - total: dcount - } -} - async function cleanArticle (entry, shortenContent = false) { let poster = await API.User.get(entry.user_id) let article = { @@ -76,7 +57,7 @@ const News = { } count = count[0].ids - let paginated = Pagination(perPage, parseInt(count), page) + let paginated = API.Pagination(perPage, parseInt(count), page) let news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage) let articles = [] diff --git a/server/routes/admin.js b/server/routes/admin.js index c8d42ec..4262ced 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -1,20 +1,19 @@ import express from 'express' -import multiparty from 'multiparty' import config from '../../scripts/load-config' import wrap from '../../scripts/asyncRoute' -import API from '../api' +import {User} from '../api' +import API from '../api/admin' import News from '../api/news' -import Image from '../api/image' -import APIExtern from '../api/external' const router = express.Router() const apiRouter = express.Router() +// Check for privilege required to access the admin panel router.use(wrap(async (req, res, next) => { if (!req.session.user) return res.redirect('/login') if (!req.session.privilege) { - let u = await API.User.get(req.session.user) + let u = await User.get(req.session.user) req.session.privilege = u.nw_privilege } @@ -26,6 +25,49 @@ router.use(wrap(async (req, res, next) => { next() })) +/* ================ + * ASK PASSWORD + * ================ + */ + +apiRouter.get('/access', (req, res) => { + if (!req.session.accesstime || req.session.accesstime < Date.now()) { + return res.status(401).jsonp({error: 'Access expired'}) + } + + res.jsonp({access: req.session.accesstime - Date.now()}) +}) + +// Post password to continue +router.post('/', wrap(async (req, res, next) => { + if (!req.body.password) return next() + + if (req.body.csrf !== req.session.csrf) { + req.flash('message', {error: true, text: 'Invalid session token'}) + return next() + } + + let passReady = await User.Login.password(req.session.user, req.body.password) + if (passReady) { + req.session.accesstime = Date.now() + 300000 // 5 minutes + return res.redirect('/admin') + } else { + req.flash('message', {error: true, text: 'Invalid password'}) + } + + next() +})) + +// Ensure that the admin panel is not kept open for prolonged time +router.use(wrap(async (req, res, next) => { + if (req.session.accesstime) { + if (req.session.accesstime > Date.now()) return next() + delete req.session.accesstime + } + + res.render('user/password', {post: '/admin'}) +})) + /* ========= * VIEWS * ========= @@ -44,6 +86,16 @@ router.get('/oauth2', wrap(async (req, res) => { * ======= */ +apiRouter.get('/users', wrap(async (req, res) => { + let page = parseInt(req.query.page) + if (isNaN(page) || page < 1) { + page = 1 + } + + let users = await API.getAllUsers(page) + res.jsonp(users) +})) + router.use('/api', apiRouter) module.exports = router diff --git a/server/routes/index.js b/server/routes/index.js index c4b0fb0..a402b9c 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -684,10 +684,7 @@ router.get('/partials/:view', wrap(async (req, res, next) => { */ router.get('/logout', wrap(async (req, res) => { - if (req.session.user) { - delete req.session.user - } - + req.session.destroy() res.redirect('/') })) diff --git a/src/script/admin.js b/src/script/admin.js index 33414da..732861d 100644 --- a/src/script/admin.js +++ b/src/script/admin.js @@ -1,3 +1,72 @@ window.$ = require('jquery') +var Mustache = require('mustache') -$(window).ready(function () {}) +function buildTemplateScript (id, ctx) { + var tmpl = $('#' + id) + if (!tmpl.length) return null + var data = tmpl.html() + Mustache.parse(data) + return Mustache.render(data, ctx) +} + +function paginationButton (pages) { + var html = '
' + html += 'Page ' + pages.page + ' of ' + pages.pages + '' + if (pages.page > 1) { + html += '
Previous
' + } + for (var i = 0; i < pages.pages; i++) { + html += '
' + (i + 1) + '
' + } + if (pages.pages > pages.page) { + html += '
Next
' + } + html += '
' + return html +} + +function loadUsers (page) { + $.ajax({ + type: 'get', + url: '/admin/api/users', + data: {page: page}, + success: function (data) { + $('#userlist').html('') + if (data.error) { + $('#userlist').html('
' + data.error + '
') + return + } + + var pgbtn = paginationButton(data.page) + $('#userlist').append(pgbtn) + $('.pgn .button').click(function (e) { + var pgnum = $(this).data('page') + if (pgnum == null) return + loadUsers(parseInt(pgnum)) + }) + + for (var u in data.users) { + var user = data.users[u] + user.created_at = new Date(user.created_at) + var tmp = buildTemplateScript('user', user) + $('#userlist').append(tmp) + } + } + }) +} + +$(document).ready(function () { + if ($('#userlist').length) { + loadUsers(1) + } + + setInterval(function () { + $.get({ + url: '/admin/api/access', + success: function (data) { + if (data && data.access) return + window.location.reload() + } + }) + }, 30000) +}) diff --git a/src/style/admin.styl b/src/style/admin.styl index 5e66db0..cacc95b 100644 --- a/src/style/admin.styl +++ b/src/style/admin.styl @@ -1,13 +1,11 @@ body margin: 0 - background-color: #f5f5f5 - font-family: Helvetica nav display: block height: 60px - background-color: #6b6b6b - border-bottom: 5px solid #383838 + background-color: #00BCD4 + border-bottom: 5px solid #0096a9 ul display: inline-block margin: 0 @@ -20,14 +18,14 @@ nav display: inline-block a font-size: 180% - padding: 15px + padding: 16px line-height: 2 - color: #ddd + color: #fff text-decoration: none text-transform: uppercase font-weight: bold &:hover - background-color: #868686 + background-color: #00c9e2 .wrapper min-height: 100vh @@ -35,3 +33,17 @@ nav .container overflow: hidden padding: 10px + background-color: #fff + min-height: 100vh + +.user + min-height: 180px + .avatar + float: left + .info + margin-left: 170px + .display_name + font-weight: bold + font-size: 120% + .username + font-size: 80% diff --git a/views/admin/index.pug b/views/admin/index.pug index 1eae901..4b901dd 100644 --- a/views/admin/index.pug +++ b/views/admin/index.pug @@ -4,3 +4,8 @@ block body .container .content h1 Welcome to the Admin Panel + .left + .users + h3 Registered Users + #userlist + .right diff --git a/views/admin/layout.pug b/views/admin/layout.pug index f9a74ef..096dec7 100644 --- a/views/admin/layout.pug +++ b/views/admin/layout.pug @@ -1,6 +1,9 @@ html head meta(charset="utf8") + link(rel="stylesheet", type="text/css", href="https://fonts.googleapis.com/css?family=Open+Sans") + link(rel="stylesheet", type="text/css", href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css") + link(rel="stylesheet", type="text/css", href="/style/main.css") link(rel="stylesheet", type="text/css", href="/style/admin.css") script. window.variables = { @@ -15,7 +18,7 @@ html nav ul li - a.logo(href="/") Icy Network + a.navlogo(href="/") Icy Network li a(href="/admin/") Home li @@ -25,3 +28,30 @@ html a(href="/user/manage") #{user.display_name} .wrapper block body + .templates + script(type="x-tmpl-mustache" id="user"). +
+
+ {{#avatar_file}} + + {{/avatar_file}} + {{^avatar_file}} + + {{/avatar_file}} +
+
+
+ {{^activated}} +
+ {{/activated}} +
+
{{display_name}}
+
{{username}}
+ +
Privilege: {{nw_privilege}} points
+
{{created_at}}
+ {{^password}} +
Used external login
+ {{/password}} +
+
diff --git a/views/user/password.pug b/views/user/password.pug index a4e4ee0..48835d8 100644 --- a/views/user/password.pug +++ b/views/user/password.pug @@ -15,7 +15,7 @@ block body else .message span #{message.text} - form#loginForm(method="POST", action="") + form#loginForm(method="POST", action=post) input(type="hidden", name="csrf", value=csrf) label(for="password") Password input(type="password", name="password", id="password")