admin oauth2 client management

This commit is contained in:
Evert Prants 2017-12-05 18:59:17 +02:00
parent 73049447cd
commit 04e0c77a46
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 268 additions and 24 deletions

View File

@ -143,8 +143,6 @@ const API = {
data = dataFilter(data, fields, ['scope', 'verified']) data = dataFilter(data, fields, ['scope', 'verified'])
if (!data) throw new Error('Missing fields') if (!data) throw new Error('Missing fields')
data.verified = (data.verified != null ? 1 : 0)
try { try {
await Models.OAuth2Client.query().patchAndFetchById(id, data) await Models.OAuth2Client.query().patchAndFetchById(id, data)
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id) await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)

View File

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

View File

@ -0,0 +1,111 @@
<template lang="pug">
modal(:show='show', @close='close')
.modal-header
h3(v-if="id > 0") Edit Client
h3(v-else) New Client
.modal-body.aligned-form
.message.error(v-if='error') {{ error }}
.cell
label(for="title") Title
input(type="text" id="title" name="title" v-model="title")
.cell
label(for="description") Description
input(type="text" id="description" name="description" v-model="description")
.cell
label(for="url") URL
input(type="text" id="url" name="url" v-model="url")
.cell
label(for="scope") Scope
input(type="text" id="scope" name="scope" v-model="scope")
.cell
label(for="redirect_url") Redirect
input(type="text" id="redirect_url" name="redirect_url" v-model="redirect_url")
.cell
label(for="verified") Verified
input(type="checkbox" id="verified" name="verified" v-model="verified")
.modal-footer.text-align
button(@click='submit') Done
button(@click='newSecret' v-if="id > 0") New Secret
button(@click='close') Cancel
</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: '',
title: '',
description: '',
scope: '',
url: '',
redirect_url: '',
verified: false
}
},
components: {
Modal
},
methods: {
close: function () {
this.$emit('close')
this.error = ''
this.title = ''
this.description = ''
this.scope = ''
this.url = ''
this.redirect_url = ''
this.verified = false
},
submit: function () {
let url = this.id === -1 ? 'new' : 'update'
this.$http.post('/admin/api/client/' + url, {
id: this.id,
title: this.title,
description: this.description,
scope: this.scope,
url: this.url,
redirect_url: this.redirect_url,
verified: this.verified,
csrf: csrfToken
}).then(data => {
this.close()
this.$root.$emit('reload_clients')
}).catch(err => {
console.error(err)
if (err.body && err.body.error) this.error = err.body.error
})
},
newSecret: function () {
this.$http.post('/admin/api/client/new_secret/' + this.id).then(data => {
alert('New secret generated.')
this.$root.$emit('reload_clients')
}).catch(err => {
this.error = 'Failed to generate new secret.'
})
}
},
watch: {
id: function () {
if (this.id <= 0) return
this.$http.get('/admin/api/client/' + this.id).then(data => {
let dr = data.body
this.title = dr.title
this.description = dr.description
this.scope = dr.scope
this.url = dr.url
this.redirect_url = dr.redirect_url
this.verified = dr.verified
}).catch(err => {
alert('Failed to fetch client data')
this.close()
})
}
}
}
</script>

View File

@ -0,0 +1,85 @@
<template lang="pug">
#clientlist
button(@click="editing = -1") New Client
.pgn
span.pagenum Page {{ pagination.page }} of {{ pagination.pages }}
.button(v-if='pagination.page > 1', v-on:click='getClients(pagination.page - 1)') Previous
.button(v-for='n in pagination.pages', v-on:click='getClients(n)', v-bind:class='{active: n == pagination.page}') {{ n }}
.button(v-if='pagination.page < pagination.pages', v-on:click='getClients(pagination.page + 1)') Next
.list.client
.message.error(v-if="error") {{ error }}
.application.list-item(v-else v-for="client in clients")
.picture
img(v-if="client.icon" :src="'/usercontent/images/' + client.icon")
.noicon(v-else)
i.fa.fa-fw.fa-gears
.info
.stamps
.verified(v-if="client.verified")
i.fa.fa-fw.fa-check
.name {{ client.title }}
.description {{ client.description }}
a.url(:href='client.url', target='_blank', rel='nofollow') {{ client.url }}
.scope Scopes: {{ client.scope }}
.redirect_url Redirect: {{ client.redirect_url }}
.id Client ID: {{ client.id }}
.secret
| Client Secret:
#showbutton Hover
#hiddensecret {{ client.secret }}
.button.edit(@click="editing = client.id") Edit
.button.delete(@click="deleteClient(client.id)") Delete
client-modal(:show="editing != 0" @close='editing = 0', :id='editing')
</template>
<script type="text/javascript">
import ClientModal from './ClientModal.vue'
export default {
data: function () {
return {
clients: [],
pagination: {
offset: 0,
page: 1,
pages: 1,
perPage: 6,
total: 0
},
editing: 0,
error: ''
}
},
components: {
ClientModal
},
methods: {
getClients: function (page) {
this.pagination.total = 0
this.error = ''
this.$http.get('/admin/api/clients?page=' + page).then(data => {
if (data.body && data.body.error) {
this.error = data.body.error
return
}
this.pagination = data.body.page
this.clients = data.body.clients
})
},
deleteClient: function (id) {
this.$http.post('/admin/api/client/delete/' + id).then(data => {
this.getClients(1)
})
}
},
mounted: function () {
this.getClients(1)
this.$root.$on('reload_clients', () => {
this.getClients(1)
})
}
}
</script>

View File

@ -1,8 +1,20 @@
<template lang="pug"> <template lang="pug">
.root .root
h1 Manage OAuth2 Clients h1 Manage OAuth2 Clients
o-auth-clients
</template> </template>
<script type="text/javascript"> <script type="text/javascript">
export default {} import OAuthClients from '../component/OAuthClients.vue'
export default {
methods: {
newClient: function () {
alert('not yet')
}
},
components: {
OAuthClients
}
}
</script> </script>

View File

@ -10,6 +10,7 @@
<script type="text/javascript"> <script type="text/javascript">
import UserList from '../component/UserList.vue' import UserList from '../component/UserList.vue'
import BanList from '../component/BanList.vue' import BanList from '../component/BanList.vue'
export default { export default {
components: { components: {
UserList, BanList UserList, BanList

View File

@ -71,11 +71,22 @@ nav
background-color: #fff background-color: #fff
.application .application
height: 200px min-height: 200px
#hiddensecret #hiddensecret
display: none display: none
&.shown color: #ff796f
display: inline-block background-color: #f1f1f1
padding: 5px
min-width: 250px
#showbutton
font-style: italic
display: inline-block
cursor: pointer
&:hover > #hiddensecret
display: block
.link .link
color: green color: green
cursor: pointer cursor: pointer
@ -97,7 +108,7 @@ nav
vertical-align: middle vertical-align: middle
.modal-container .modal-container
width: 300px width: 360px
margin: 0px auto margin: 0px auto
padding: 20px 30px padding: 20px 30px
background-color: #fff background-color: #fff
@ -113,8 +124,15 @@ nav
.modal-body .modal-body
margin: 20px 0 margin: 20px 0
.modal-default-button .modal-footer
float: right min-height: 50px
button
margin: 5px
display: inline-block
&.text-align
text-align: center
/* /*
* The following styles are auto-applied to elements with * The following styles are auto-applied to elements with
@ -125,6 +143,16 @@ nav
* these styles. * these styles.
*/ */
.fade-enter-active, .fade-leave-active
transition-property: opacity
transition-duration: .25s
.fade-enter-active
transition-delay: .25s
.fade-enter, .fade-leave-active
opacity: 0
.modal-enter .modal-enter
opacity: 0 opacity: 0
@ -135,9 +163,6 @@ nav
-webkit-transform: scale(1.1) -webkit-transform: scale(1.1)
transform: scale(1.1) transform: scale(1.1)
.modal-footer
min-height: 50px
form form
.message .message
display: none display: none

View File

@ -211,6 +211,7 @@ button, .button, input[type="submit"]
.button .button
display: inline-block display: inline-block
margin-right: 5px
.boxcont .boxcont
.box .box
@ -568,6 +569,17 @@ select
margin-left: 5px margin-left: 5px
border-radius: 5px border-radius: 5px
.aligned-form
.cell
margin-top: 10px
label
width: 120px
float: left
margin: 0
padding: 8px 0
input[type="checkbox"]
margin-top: 10px
@media all and (max-width: 800px) @media all and (max-width: 800px)
.navigator .navigator
padding: 0 10px padding: 0 10px

View File

@ -3,8 +3,6 @@ extends layout.pug
block body block body
.container .container
.content .content
router-view transition(name="fade")
router-view
.templates
script(type="text/x-template" id="ban-modal-template").