Use bootstrap on admin panel

This commit is contained in:
Evert Prants 2018-02-13 22:12:45 +02:00
parent 60e9b00a74
commit 67f303b382
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
16 changed files with 225 additions and 274 deletions

View File

@ -4,24 +4,24 @@
.noactive.stamp(title='Expired', v-if='expired') .noactive.stamp(title='Expired', v-if='expired')
i.fa.fa-fw.fa-ban i.fa.fa-fw.fa-ban
.info .info
.section .row
span.key User .col-3 User
span.value {{ user.display_name }} .col {{ user.display_name }}
.section .row
span.key Admin .col-3 Admin
span.value {{ admin.display_name }} .col {{ admin.display_name }}
.section .row
span.key Reason .col-3 Reason
span.value {{ reason }} .col {{ reason }}
.section .row
span.key Placed .col-3 Placed
span.value {{ new Date(created_at).toString() }} .col {{ new Date(created_at).toString() }}
.section .row
span.key Expires .col-3 Expires
span.value(v-if='expires_at') {{ new Date(expires_at).toString() }} .col(v-if='expires_at') {{ new Date(expires_at).toString() }}
span.value(v-else='v-else') .col(v-else='v-else')
b This ban is permanent. b This ban is permanent.
.button.remove(@click='$parent.$emit("pardon", id)') .btn.btn-success.remove.mt-3(@click='$parent.$emit("pardon", id)')
i.fa.fa-fw.fa-check i.fa.fa-fw.fa-check
| Pardon | Pardon
</template> </template>

View File

@ -1,7 +1,7 @@
<template lang="pug"> <template lang="pug">
#banlist #banlist
h3 Bans ({{pagination.total}}) h3 Bans ({{pagination.total}})
.message.error(v-if='error') {{ error }} .alert.alert-danger(v-if='error') {{ error }}
.entry(v-else) .entry(v-else)
pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getBans") pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getBans")
.list.bans .list.bans

View File

@ -2,18 +2,18 @@
modal(:show='show', @close='close') modal(:show='show', @close='close')
.modal-header .modal-header
h3 Ban user h3 Ban user
.modal-body.aligned-form .modal-body
.message.error(v-if='error') {{ error }} .alert.alert-danger(v-if='error') {{ error }}
input(type='hidden', name='user_id', :value='id') input(type='hidden', name='user_id', :value='id')
.cell .form-group
label(for='reason') Reason label(for='reason') Reason
input#reason(type='text', name='reason', v-model='reason') input.form-control#reason(type='text', name='reason', v-model='reason')
.cell .form-group
label(for='expires_at') Expires label(for='expires_at') Expires
input#expires_at(type='date', name='expires_at', v-model='expires_at') input.form-control#expires_at(type='date', name='expires_at', v-model='expires_at')
.modal-footer.text-align .modal-footer.text-right
button(@click='submit') Ban button.btn.btn-primary(@click='submit') Ban
button(@click='close') Cancel button.btn.btn-secondary(@click='close') Cancel
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -4,29 +4,29 @@
h3(v-if="id > 0") Edit Client h3(v-if="id > 0") Edit Client
h3(v-else) New Client h3(v-else) New Client
.modal-body.aligned-form .modal-body.aligned-form
.message.error(v-if='error') {{ error }} .alert.alert-danger(v-if='error') {{ error }}
.cell .form-group
label(for="title") Title label(for="title") Title
input(type="text" id="title" name="title" v-model="title") input.form-control(type="text" id="title" name="title" v-model="title")
.cell .form-group
label(for="description") Description label(for="description") Description
input(type="text" id="description" name="description" v-model="description") input.form-control(type="text" id="description" name="description" v-model="description")
.cell .form-group
label(for="url") URL label(for="url") URL
input(type="text" id="url" name="url" v-model="url") input.form-control(type="text" id="url" name="url" v-model="url")
.cell .form-group
label(for="scope") Scope label(for="scope") Scope
input(type="text" id="scope" name="scope" v-model="scope") input.form-control(type="text" id="scope" name="scope" v-model="scope")
.cell .form-group
label(for="redirect_url") Redirect label(for="redirect_url") Redirect
input(type="text" id="redirect_url" name="redirect_url" v-model="redirect_url") input.form-control(type="text" id="redirect_url" name="redirect_url" v-model="redirect_url")
.cell .form-check
label(for="verified") Verified input.form-check-input(type="checkbox" id="verified" name="verified" v-model="verified")
input(type="checkbox" id="verified" name="verified" v-model="verified") label.form-check-label(for="verified") Verified
.modal-footer.text-align .modal-footer.text-right
button(@click='submit') Done button.btn.btn-primary(@click='submit') Done
button(@click='newSecret' v-if="id > 0") New Secret button.btn.btn-danger(@click='newSecret' v-if="id > 0") New Secret
button(@click='close') Cancel button.btn.btn-secondary(@click='close') Cancel
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -1,10 +1,11 @@
<template lang="pug"> <template lang="pug">
.application.list-item .application.list-item.row
.picture .col-2
img(v-if="icon" :src="'/usercontent/images/' + icon") .picture
.noicon(v-else) img(v-if="icon" :src="'/usercontent/images/' + icon")
i.fa.fa-fw.fa-gears .noicon(v-else)
.info i.fa.fa-fw.fa-gears
.info.col
.stamps .stamps
.verified.stamp(v-if="verified") .verified.stamp(v-if="verified")
i.fa.fa-fw.fa-check i.fa.fa-fw.fa-check
@ -25,30 +26,30 @@
.description {{ description }} .description {{ description }}
a.url(:href='url', target='_blank', rel='nofollow') {{ url }} a.url(:href='url', target='_blank', rel='nofollow') {{ url }}
.section .row
span.key Scopes .col-2 Scopes
span.value {{ scope }} .col {{ scope }}
.section .row
span.key Redirect .col-2 Redirect
span.value {{ redirect_url }} .col {{ redirect_url }}
.section .row
span.key Client ID .col-2 Client ID
span.value {{ id }} .col {{ id }}
.section .row
span.key Secret .col-2 Secret
span.value Client Secret: .col Client Secret:
#showbutton(@click="secretShown = !secretShown") #showbutton(@click="secretShown = !secretShown")
span(v-show="!secretShown") Click here to reveal secret span(v-show="!secretShown") Click here to reveal secret
#hiddensecret(v-show="secretShown") {{ secret }} #hiddensecret(v-show="secretShown") {{ secret }}
.section .row
span.key Grants .col-2 Grants
span.value {{ grants }} .col {{ grants }}
.section .row
span.key Owner .col-2 Owner
span.value {{ user.display_name }} .col {{ user.display_name }}
.section .row
span.key Created .col-2 Created
span.value {{ new Date(created_at).toString() }} .col {{ new Date(created_at).toString() }}
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
#clientlist #clientlist
button(@click="editing = -1") New Client button.btn.btn-primary.mt-1(@click="editing = -1") New Client
.message.error(v-if="error") {{ error }} .message.error(v-if="error") {{ error }}
.entry(v-else) .entry(v-else)
pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getClients") pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getClients")

View File

@ -1,9 +1,11 @@
<template lang="pug"> <template lang="pug">
.pgn ul.pagination.mt-4
span.pagenum Page {{ page }} of {{ pages }} li.page-item(v-bind:class='{disabled: page == 1}')
.button(v-if='page > 1' v-on:click='$emit("page", page - 1)') Previous a.page-link(href="#" v-on:click='$emit("page", page - 1)') Previous
.button(v-for='n in pages' v-on:click='$emit("page", n)' v-bind:class='{active: n == page}') {{ n }} li.page-item(v-for='n in pages' v-bind:class='{active: n == page}')
.button(v-if='page < pages' v-on:click='$emit("page", page + 1)') Next a.page-link(href="#" v-on:click='$emit("page", n)') {{ n }}
li.page-item(v-bind:class='{disabled: page == pages}')
a.page-link(v-on:click='$emit("page", page + 1)') Next
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -1,9 +1,14 @@
<template lang="pug"> <template lang="pug">
.user.list-item .user.list-item.locked-account(v-if='locked')
.avatar h1 Locked account
img(v-if='avatar_file', v-bind:src="'/usercontent/images/' + avatar_file") .description This account has been locked
img(v-else='v-else', src='/static/image/avatar.png') .id User ID: {{ id }}
.info .row.user.list-item(v-else='v-else')
.col-4
.avatar
img(v-if='avatar_file', v-bind:src="'/usercontent/images/' + avatar_file")
img(v-else='v-else', src='/static/image/avatar.png')
.col.info
.stamps .stamps
.stamp(title="Used an external login" v-if="!password") .stamp(title="Used an external login" v-if="!password")
i.fa.fa-fw.fa-sign-out i.fa.fa-fw.fa-sign-out
@ -35,21 +40,25 @@
.action(v-on:click='resetPassword') .action(v-on:click='resetPassword')
i.fa.fa-fw.fa-envelope i.fa.fa-fw.fa-envelope
|&nbsp;Password Email |&nbsp;Password Email
.separator
.action(v-on:click='$parent.$emit("lock", id)' v-if="id != 1 && nw_privilege < 2")
i.fa.fa-fw.fa-lock
|&nbsp;Lock Account
.display_name {{ display_name }} .display_name {{ display_name }}
.name {{ id }} - {{ username }} ({{ uuid }}) .name {{ id }} - {{ username }} ({{ uuid }})
.section .row
span.key Email .col-4 Email
span.value {{ email }} .col {{ email }}
.section .row
span.key Privilege .col-4 Privilege
span.value {{ nw_privilege }} .col {{ nw_privilege }}
.section .row
span.key Last IP .col-4 Last IP
span.value {{ ip_address }} .col {{ ip_address }}
.section .row
span.key Registered .col-4 Registered
span.value {{ new Date(created_at).toString() }} .col {{ new Date(created_at).toString() }}
</template> </template>
<script type="text/javascript"> <script type="text/javascript">
@ -57,7 +66,7 @@
const csrfToken = document.querySelector('meta[name="csrf-token"]').content const csrfToken = document.querySelector('meta[name="csrf-token"]').content
export default { export default {
props: ['avatar_file', 'activated', 'display_name', 'id', 'username', 'uuid', 'email', 'nw_privilege', 'created_at', 'password', 'bannable', 'ip_address', 'totp_enabled'], props: ['avatar_file', 'activated', 'display_name', 'id', 'username', 'uuid', 'email', 'nw_privilege', 'created_at', 'password', 'bannable', 'ip_address', 'totp_enabled', 'locked'],
directives: { directives: {
onClickaway: onClickaway, onClickaway: onClickaway,
}, },

View File

@ -3,12 +3,13 @@
h3 Registered Users ({{ pagination.total }}) h3 Registered Users ({{ pagination.total }})
pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getUsers") pagination(:page="pagination.page" :pages="pagination.pages" v-on:page="getUsers")
.list.users .list.users
.searchbox .form-group
input(v-model="search" placeholder="Begin typing a name or email address..") input.form-control(v-model="search" placeholder="Begin typing a name or email address..")
.message.error(v-if="error") {{ error }} .alert.alert-danger(v-if="error") {{ error }}
user(v-else v-for='user in users' v-bind="user" :key="user.id") user(v-else v-for='user in users' v-bind="user" :key="user.id")
ban-modal(:show='banning' @close='banning = 0' :id='banning') ban-modal(:show='banning' @close='banning = 0' :id='banning')
user-modal(:show='editing' @close='editing = 0' :id='editing') user-modal(:show='editing' @close='editing = 0' :id='editing')
user-lock-modal(:show='locking' @close='locking = 0' :id='locking')
</template> </template>
<script> <script>
@ -16,6 +17,7 @@
import User from './User.vue' import User from './User.vue'
import BanModal from './BanModal.vue' import BanModal from './BanModal.vue'
import UserModal from './UserModal.vue' import UserModal from './UserModal.vue'
import UserLockModal from './UserLockModal.vue'
import qs from 'querystring' import qs from 'querystring'
const csrfToken = document.querySelector('meta[name="csrf-token"]').content const csrfToken = document.querySelector('meta[name="csrf-token"]').content
@ -33,12 +35,13 @@
users: [], users: [],
banning: 0, banning: 0,
editing: 0, editing: 0,
locking: 0,
search: '', search: '',
error: '' error: ''
} }
}, },
components: { components: {
Pagination, User, BanModal, UserModal Pagination, User, BanModal, UserModal, UserLockModal
}, },
methods: { methods: {
getUsers: function (page) { getUsers: function (page) {
@ -84,6 +87,10 @@
this.editing = id this.editing = id
}) })
this.$on('lock', function (id) {
this.locking = id
})
this.$root.$on('reload_users', () => { this.$root.$on('reload_users', () => {
this.getUsers(this.pagination.page) this.getUsers(this.pagination.page)
}) })

View File

@ -0,0 +1,50 @@
<template lang="pug">
modal(:show='show', @close='close')
.modal-header
h3 Lock User
.modal-body.aligned-form
.alert.alert-danger(v-if='error') {{ error }}
p Are you sure you want to lock this user?
p This user will not be able to log in and will not be considered a registered user.
p This action cannot be reverted (user records will be overwritten).
.modal-footer.text-right
button.btn.btn-danger(@click='submit')
i.fa.fa-fw.fa-lock
|&nbsp;Yes
button.btn.btn-secondary(@click='close') No
</template>
<script type="text/javascript">
import Modal from './Modal.vue'
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
export default {
props: ['show', 'id'],
data: function () {
return {
error: ''
}
},
components: {
Modal
},
methods: {
close: function () {
this.$emit('close')
this.error = ''
},
submit: function () {
this.$http.post('/admin/api/user/lock', {
user_id: this.id,
csrf: csrfToken
}).then(data => {
this.close()
this.$root.$emit('reload_users')
}).catch(err => {
console.error(err)
if (err.body && err.body.error) this.error = err.body.error
})
}
}
}
</script>

View File

@ -2,27 +2,27 @@
modal(:show='show', @close='close') modal(:show='show', @close='close')
.modal-header .modal-header
h3 Edit User h3 Edit User
.modal-body.aligned-form .modal-body
.message.error(v-if='error') {{ error }} .alert.alert-danger(v-if='error') {{ error }}
.cell .form-group
label(for="username") Username label(for="username") Username
input(type="text" id="username" name="username" v-model="username") input.form-control(type="text" id="username" name="username" v-model="username")
.cell .form-group
label(for="display_name") Display Name label(for="display_name") Display Name
input(type="text" id="display_name" name="display_name" v-model="display_name") input.form-control(type="text" id="display_name" name="display_name" v-model="display_name")
.cell .form-group
label(for="email") Email label(for="email") Email
input(type="email" id="email" name="email" v-model="email") input.form-control(type="email" id="email" name="email" v-model="email")
.cell .form-group
label(for="privilege") Privilege label(for="privilege") Privilege
input(type="range" min="0" max="5" step="1" id="privilege" name="privilege" v-model="nw_privilege") input(type="range" min="0" max="5" step="1" id="privilege" name="privilege" v-model="nw_privilege")
span {{ nw_privilege }} span {{ nw_privilege }}
.cell .form-check
label(for="activated") Activated input.form-check-input(type="checkbox" id="activated" name="activated" v-model="activated")
input(type="checkbox" id="activated" name="activated" v-model="activated") label.form-check-label(for="activated") Activated
.modal-footer.text-align .modal-footer.text-right
button(@click='submit') Done button.btn.btn-primary(@click='submit') Done
button(@click='close') Cancel button.btn.btn-secondary(@click='close') Cancel
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -1,10 +1,11 @@
<template lang="pug"> <template lang="pug">
.root .root
h1 Welcome to the Admin Panel h1 Welcome to the Admin Panel
.left .row.mt-4
user-list .col
.right user-list
ban-list .col
ban-list
</template> </template>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -2,62 +2,20 @@ body
margin: 0 margin: 0
font-family: sans-serif font-family: sans-serif
nav .stamps
display: block float: right
height: 60px .stamp
background-color: #00BCD4 color: #2196F3
border-bottom: 5px solid #0096a9 padding: 5px
ul font-size: 120%
display: inline-block display: inline-block
margin: 0
padding: 0
list-style-type: none
&.right
float: right
li
height: 60px
display: inline-block
a
font-size: 180%
padding: 16px
line-height: 2
color: #fff
text-decoration: none
text-transform: uppercase
font-weight: bold
&:hover
background-color: #00c9e2
.wrapper &.noactive.stamp
min-height: 100vh color: #f70000
overflow: hidden
.container
overflow: hidden
padding: 10px
background-color: #fff
min-height: 100vh
.left, .right
width: 48%
display: inline-block
.right
float: right
.stamps
float: right
.stamp
color: #2196F3
padding: 5px
font-size: 120%
display: inline-block
&.noactive.stamp
color: #f70000
.user .user
min-height: 160px min-height: 160px
.avatar
float: left
.info .info
margin-left: 170px
.display_name .display_name
font-weight: bold font-weight: bold
font-size: 120% font-size: 120%
@ -80,7 +38,7 @@ nav
background-color: #fff background-color: #fff
.application .application
min-height: 220px height: auto
#hiddensecret #hiddensecret
color: #ff796f color: #ff796f
@ -96,6 +54,12 @@ nav
color: green color: green
cursor: pointer cursor: pointer
display: inline-block display: inline-block
.picture
float: none
.info
margin: 0
.dropdown-wrapper .dropdown-wrapper
border-radius: 5px border-radius: 5px
@ -133,14 +97,6 @@ nav
margin-top: 10px margin-top: 10px
border-bottom: 1px solid #ddd border-bottom: 1px solid #ddd
margin-bottom: 9px margin-bottom: 9px
.searchbox
margin: 10px 0
input
font-size: 140%
display: block
width: 100%
.modal-mask .modal-mask
position: fixed position: fixed
@ -181,9 +137,6 @@ nav
margin: 5px margin: 5px
display: inline-block display: inline-block
&.text-align
text-align: center
.fade-enter-active, .fade-leave-active .fade-enter-active, .fade-leave-active
transition-property: opacity transition-property: opacity
transition-duration: .25s transition-duration: .25s

View File

@ -1,8 +1,7 @@
extends layout.pug extends layout.pug
block body block body
.container .container.mt-4
.content transition(name="fade")
transition(name="fade") router-view
router-view

View File

@ -3,6 +3,7 @@ html
meta(charset="utf8") meta(charset="utf8")
meta(name="csrf-token", content=csrf) meta(name="csrf-token", content=csrf)
block links block links
link(rel="stylesheet", type="text/css", href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css")
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="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/main.css")
link(rel="stylesheet", type="text/css", href="/style/admin.css") link(rel="stylesheet", type="text/css", href="/style/admin.css")
@ -16,17 +17,23 @@ html
body body
#app #app
block navigation block navigation
nav nav.navbar.navbar-expand-lg.navbar-light.bg-light.sticky-top
ul a.navbar-brand(href="/")
li img(src="/static/image/icynet-icon.svg" width="30" heigth="30")
a.navlogo(href="/") Icy Network |Administration
li
router-link(to="/") Home button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navCollapse" aria-controls="navCollapse" aria-expanded="false" aria-label="Toggle navigation")
li span.navbar-toggler-icon
router-link(to="/oauth2") OAuth2
ul.right .navbar-collapse.collapse#navCollapse
li ul.mr-auto.navbar-nav
a(href="/user/manage") #{user.display_name} router-link(tag="li" class="nav-item" active-class="active" to="/" exact)
a.nav-link Home
router-link(tag="li" class="nav-item" active-class="active" to="/oauth2")
a.nav-link OAuth2
ul.navbar-nav.my-2.my-lg-0
li.nav-item
a.nav-link(href="/user/manage") #{user.display_name}
.wrapper .wrapper
block body block body

View File

@ -1,78 +0,0 @@
extends layout.pug
block body
.container
.content
h1 Manage OAuth2 Clients
.button(id="new") New Client
#clientlist
.templates
script(type="x-tmpl-mustache" id="client").
<div class="application" id="client-{{id}}">
<div class="picture">
{{#icon}}
<img src="/usercontent/images/{{icon}}">
{{/icon}}
{{^icon}}
<div class="noicon"><i class="fa fa-fw fa-gears"></i></div>
{{/icon}}
</div>
<div class="info">
<div class="stamps">
{{#verified}}
<div class="verified"><i class="fa fa-fw fa-check"></i></div>
{{/verified}}
</div>
<div class="name">{{title}}</div>
<div class="description">{{description}}</div>
<a class="url" href="{{url}}" target="_blank" rel="nofollow">{{url}}</a>
<div class="scope">Scopes: {{scope}}</div>
<div class="redirect_url">Redirect: {{redirect_url}}</div>
<div class="id">Client ID: {{id}}</div>
<div class="secret">Client Secret: <div id="hiddensecret">{{secret}}</div>
<div class="link" id="showbutton" onclick="$(this).parent().find('#hiddensecret').toggleClass('shown')">Show</div>
</div>
<div class="button edit" data-client="{{id}}">Edit</div>
<div class="button delete" data-client="{{id}}">Delete</div>
<div class="button newsecret" data-client="{{id}}">New Secret</div>
</div>
</div>
script(type="x-tmpl-mustache" id="clientEdit").
<form id="ffsubmit">
<div class="message error"></div>
<input type="hidden" name="id" value="{{id}}">
<input type="hidden" name="csrf" value="#{csrf}">
<label for="title">Title</label>
<input type="text" id="title" name="title" value="{{title}}">
<label for="description">Description</label>
<input type="text" id="description" name="description" value="{{description}}">
<label for="url">URL</label>
<input type="text" id="url" name="url" value="{{url}}">
<label for="scope">Scope</label>
<input type="text" id="scope" name="scope" value="{{scope}}">
<label for="redirect_url">Redirect</label>
<input type="text" id="redirect_url" name="redirect_url" value="{{redirect_url}}">
<label for="verified">Verified</label>
<input type="checkbox" id="verified" name="verified" {{#verified}}checked{{/verified}}>
<input type="submit" value="Edit">
</form>
script(type="x-tmpl-mustache" id="clientNew").
<form id="fnsubmit">
<div class="message error"></div>
<input type="hidden" name="csrf" value="#{csrf}">
<label for="title">Title</label>
<input type="text" id="title" name="title">
<label for="description">Description</label>
<input type="text" id="description" name="description">
<label for="url">URL</label>
<input type="text" id="url" name="url">
<label for="scope">Scope</label>
<input type="text" id="scope" name="scope">
<label for="redirect_url">Redirect</label>
<input type="text" id="redirect_url" name="redirect_url">
<input type="submit" value="Create">
</form>
script(type="x-tmpl-mustache" id="clientRemove").
<p>Are you sure?</p>
<div class="button" onclick="window.Dialog.close()">No</div>
<div class="button" id="fremove">Yes, I'm sure</div>