admin panel work a bit
This commit is contained in:
parent
f8990ec7a6
commit
70cbbecec2
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('/')
|
||||
}))
|
||||
|
||||
|
|
|
@ -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 = '<div class="pgn">'
|
||||
html += '<span class="pagenum">Page ' + pages.page + ' of ' + pages.pages + '</span>'
|
||||
if (pages.page > 1) {
|
||||
html += '<div class="button" data-page="' + (pages.page - 1) + '">Previous</div>'
|
||||
}
|
||||
for (var i = 0; i < pages.pages; i++) {
|
||||
html += '<div class="button" data-page="' + (i + 1) + '">' + (i + 1) + '</div>'
|
||||
}
|
||||
if (pages.pages > pages.page) {
|
||||
html += '<div class="button" data-page="' + (pages.page + 1) + '">Next</div>'
|
||||
}
|
||||
html += '</div>'
|
||||
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('<div class="message error">' + data.error + '</div>')
|
||||
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)
|
||||
})
|
||||
|
|
|
@ -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%
|
||||
|
|
|
@ -4,3 +4,8 @@ block body
|
|||
.container
|
||||
.content
|
||||
h1 Welcome to the Admin Panel
|
||||
.left
|
||||
.users
|
||||
h3 Registered Users
|
||||
#userlist
|
||||
.right
|
||||
|
|
|
@ -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").
|
||||
<div class="user" id="user-{{id}}">
|
||||
<div class="avatar">
|
||||
{{#avatar_file}}
|
||||
<img src="/usercontent/images/{{avatar_file}}">
|
||||
{{/avatar_file}}
|
||||
{{^avatar_file}}
|
||||
<img src="/static/image/avatar.png">
|
||||
{{/avatar_file}}
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="stamps">
|
||||
{{^activated}}
|
||||
<div class="noactive"><i class="fa fa-fw fa-envelope"></i></div>
|
||||
{{/activated}}
|
||||
</div>
|
||||
<div class="display_name">{{display_name}}</div>
|
||||
<div class="username">{{username}}</div>
|
||||
<div class="email">{{email}}</div>
|
||||
<div class="privilege">Privilege: {{nw_privilege}} points</div>
|
||||
<div class="timestamp">{{created_at}}</div>
|
||||
{{^password}}
|
||||
<div class="external"><b>Used external login</b></div>
|
||||
{{/password}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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")
|
||||
|
|
Reference in New Issue