admin panel work a bit
This commit is contained in:
parent
f8990ec7a6
commit
70cbbecec2
|
@ -3585,6 +3585,12 @@
|
||||||
"fd-slicer": "1.0.1"
|
"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": {
|
"mute-stream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
"concurrently": "^3.5.0",
|
"concurrently": "^3.5.0",
|
||||||
"eslint-plugin-import": "^2.7.0",
|
"eslint-plugin-import": "^2.7.0",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
|
"mustache": "^2.3.0",
|
||||||
"standard": "^10.0.3",
|
"standard": "^10.0.3",
|
||||||
"uglify-js": "^1.3.5",
|
"uglify-js": "^1.3.5",
|
||||||
"watch": "^1.0.2",
|
"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) => {
|
Hash: (len) => {
|
||||||
return crypto.randomBytes(len).toString('hex')
|
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: {
|
User: {
|
||||||
get: async function (identifier) {
|
get: async function (identifier) {
|
||||||
let scope = 'id'
|
let scope = 'id'
|
||||||
|
|
|
@ -7,25 +7,6 @@ function slugify (title) {
|
||||||
return title.toLowerCase().replace(/\W/g, '-').substring(0, 16)
|
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) {
|
async function cleanArticle (entry, shortenContent = false) {
|
||||||
let poster = await API.User.get(entry.user_id)
|
let poster = await API.User.get(entry.user_id)
|
||||||
let article = {
|
let article = {
|
||||||
|
@ -76,7 +57,7 @@ const News = {
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
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 news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage)
|
||||||
|
|
||||||
let articles = []
|
let articles = []
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import multiparty from 'multiparty'
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import API from '../api'
|
import {User} from '../api'
|
||||||
|
import API from '../api/admin'
|
||||||
import News from '../api/news'
|
import News from '../api/news'
|
||||||
import Image from '../api/image'
|
|
||||||
import APIExtern from '../api/external'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const apiRouter = express.Router()
|
const apiRouter = express.Router()
|
||||||
|
|
||||||
|
// Check for privilege required to access the admin panel
|
||||||
router.use(wrap(async (req, res, next) => {
|
router.use(wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return res.redirect('/login')
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
|
||||||
if (!req.session.privilege) {
|
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
|
req.session.privilege = u.nw_privilege
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +25,49 @@ router.use(wrap(async (req, res, next) => {
|
||||||
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
|
* 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)
|
router.use('/api', apiRouter)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -684,10 +684,7 @@ router.get('/partials/:view', wrap(async (req, res, next) => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get('/logout', wrap(async (req, res) => {
|
router.get('/logout', wrap(async (req, res) => {
|
||||||
if (req.session.user) {
|
req.session.destroy()
|
||||||
delete req.session.user
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/')
|
res.redirect('/')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,72 @@
|
||||||
window.$ = require('jquery')
|
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
|
body
|
||||||
margin: 0
|
margin: 0
|
||||||
background-color: #f5f5f5
|
|
||||||
font-family: Helvetica
|
|
||||||
|
|
||||||
nav
|
nav
|
||||||
display: block
|
display: block
|
||||||
height: 60px
|
height: 60px
|
||||||
background-color: #6b6b6b
|
background-color: #00BCD4
|
||||||
border-bottom: 5px solid #383838
|
border-bottom: 5px solid #0096a9
|
||||||
ul
|
ul
|
||||||
display: inline-block
|
display: inline-block
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -20,14 +18,14 @@ nav
|
||||||
display: inline-block
|
display: inline-block
|
||||||
a
|
a
|
||||||
font-size: 180%
|
font-size: 180%
|
||||||
padding: 15px
|
padding: 16px
|
||||||
line-height: 2
|
line-height: 2
|
||||||
color: #ddd
|
color: #fff
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
&:hover
|
&:hover
|
||||||
background-color: #868686
|
background-color: #00c9e2
|
||||||
|
|
||||||
.wrapper
|
.wrapper
|
||||||
min-height: 100vh
|
min-height: 100vh
|
||||||
|
@ -35,3 +33,17 @@ nav
|
||||||
.container
|
.container
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
padding: 10px
|
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
|
.container
|
||||||
.content
|
.content
|
||||||
h1 Welcome to the Admin Panel
|
h1 Welcome to the Admin Panel
|
||||||
|
.left
|
||||||
|
.users
|
||||||
|
h3 Registered Users
|
||||||
|
#userlist
|
||||||
|
.right
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
html
|
html
|
||||||
head
|
head
|
||||||
meta(charset="utf8")
|
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")
|
link(rel="stylesheet", type="text/css", href="/style/admin.css")
|
||||||
script.
|
script.
|
||||||
window.variables = {
|
window.variables = {
|
||||||
|
@ -15,7 +18,7 @@ html
|
||||||
nav
|
nav
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
a.logo(href="/") Icy Network
|
a.navlogo(href="/") Icy Network
|
||||||
li
|
li
|
||||||
a(href="/admin/") Home
|
a(href="/admin/") Home
|
||||||
li
|
li
|
||||||
|
@ -25,3 +28,30 @@ html
|
||||||
a(href="/user/manage") #{user.display_name}
|
a(href="/user/manage") #{user.display_name}
|
||||||
.wrapper
|
.wrapper
|
||||||
block body
|
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
|
else
|
||||||
.message
|
.message
|
||||||
span #{message.text}
|
span #{message.text}
|
||||||
form#loginForm(method="POST", action="")
|
form#loginForm(method="POST", action=post)
|
||||||
input(type="hidden", name="csrf", value=csrf)
|
input(type="hidden", name="csrf", value=csrf)
|
||||||
label(for="password") Password
|
label(for="password") Password
|
||||||
input(type="password", name="password", id="password")
|
input(type="password", name="password", id="password")
|
||||||
|
|
Reference in New Issue