Dashboard updates
parent
ba7c3d4234
commit
af369cdea1
53
api.go
53
api.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -361,6 +362,10 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
||||||
users = append(users, u)
|
users = append(users, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(users, func(i, j int) bool {
|
||||||
|
return users[i].ID < users[j].ID
|
||||||
|
})
|
||||||
|
|
||||||
return renderJSON(w, users)
|
return renderJSON(w, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,8 +454,8 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
||||||
c.fm.Users[u.Username] = &u
|
c.fm.Users[u.Username] = &u
|
||||||
|
|
||||||
// Set the Location header and return.
|
// Set the Location header and return.
|
||||||
|
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
w.Header().Set("Location", c.fm.RootURL()+"/api/users/"+strconv.Itoa(u.ID))
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,22 +521,32 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sid == "self" {
|
if sid == "self" {
|
||||||
if u.Password == "" {
|
if u.Password != "" {
|
||||||
return http.StatusBadRequest, errors.New("Password missing")
|
pw, err := hashPassword(u.Password)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.us.Password = pw
|
||||||
|
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "Password", pw)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pw, err := hashPassword(u.Password)
|
if u.CSS != "" {
|
||||||
if err != nil {
|
c.us.CSS = u.CSS
|
||||||
return http.StatusInternalServerError, err
|
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "CSS", u.CSS)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.us.Password = pw
|
return http.StatusBadRequest, errors.New("Password or CSS is missing")
|
||||||
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "Password", pw)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The username and the filesystem cannot be empty.
|
// The username and the filesystem cannot be empty.
|
||||||
|
@ -555,7 +570,17 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.ID = id
|
u.ID = id
|
||||||
u.Password = ouser.Password
|
|
||||||
|
if u.Password == "" {
|
||||||
|
u.Password = ouser.Password
|
||||||
|
} else {
|
||||||
|
pw, err := hashPassword(u.Password)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Password = pw
|
||||||
|
}
|
||||||
|
|
||||||
// Updates the whole User struct because we always are supposed
|
// Updates the whole User struct because we always are supposed
|
||||||
// to send a new entire object.
|
// to send a new entire object.
|
||||||
|
|
|
@ -1,11 +1,79 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="dashboard">
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
|
|
||||||
|
<router-link v-if="user.admin" to="/users">Go to User Management</router-link>
|
||||||
|
|
||||||
|
<form @submit="changePassword">
|
||||||
|
<h2>Change Password</h2>
|
||||||
|
<p><input :class="passwordClass" type="password" placeholder="Your new password" v-model="password" name="password"></p>
|
||||||
|
<p><input :class="passwordClass" type="password" placeholder="Confirm your new password" v-model="passwordConf" name="password"></p>
|
||||||
|
<p><input type="submit" value="Change Password"></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form @submit="updateCSS">
|
||||||
|
<h2>Costum Stylesheet</h2>
|
||||||
|
<textarea v-model="css" name="css"></textarea>
|
||||||
|
<p><input type="submit" value="Update"></p>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import api from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'settings'
|
name: 'settings',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
password: '',
|
||||||
|
passwordConf: '',
|
||||||
|
css: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState([ 'user' ]),
|
||||||
|
passwordClass () {
|
||||||
|
if (this.password === '' && this.passwordConf === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.password === this.passwordConf) {
|
||||||
|
return 'green'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'red'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.css = this.user.css
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changePassword (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (this.password !== this.passwordConf) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.updatePassword(this.password).then(() => {
|
||||||
|
console.log('Success')
|
||||||
|
// TODO: show success
|
||||||
|
}).catch(e => {
|
||||||
|
this.$store.commit('showError', e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateCSS (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
api.updateCSS(this.css).then(() => {
|
||||||
|
console.log('Success')
|
||||||
|
// TODO: show success
|
||||||
|
}).catch(e => {
|
||||||
|
this.$store.commit('showError', e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<form @submit="save" class="dashboard">
|
||||||
<h1>User</h1>
|
<h1 v-if="id === 0">New User</h1>
|
||||||
|
<h1 v-else>User {{ username }}</h1>
|
||||||
|
|
||||||
<p><label for="username">Username</label><input type="text" v-model="username" name="username"></p>
|
<p><label for="username">Username</label><input type="text" v-model="username" id="username"></p>
|
||||||
<p><label for="password">Password</label><input type="password" :disabled="passwordBlock" v-model="password" name="password"></p>
|
<p><label for="password">Password</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
|
||||||
<p><label for="scope">Scope</label><input type="text" v-model="scope" name="scope"></p>
|
<p><label for="scope">Scope</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2>Permissions</h2>
|
<h2>Permissions</h2>
|
||||||
|
|
||||||
|
@ -23,9 +22,7 @@
|
||||||
|
|
||||||
<p class="small">A space separated list with the available commands for this user. Example: <i>git svn hg</i>.</p>
|
<p class="small">A space separated list with the available commands for this user. Example: <i>git svn hg</i>.</p>
|
||||||
|
|
||||||
<input type="text" v-model="commands">
|
<input type="text" v-model.trim="commands">
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2>Rules</h2>
|
<h2>Rules</h2>
|
||||||
|
|
||||||
|
@ -43,16 +40,14 @@
|
||||||
<li><code>disallow /Caddyfile</code> - blocks the access to the file named <i>Caddyfile</i> on the root of the scope</li>
|
<li><code>disallow /Caddyfile</code> - blocks the access to the file named <i>Caddyfile</i> on the root of the scope</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<textarea v-model="rules"></textarea>
|
<textarea v-model.trim="rules"></textarea>
|
||||||
|
|
||||||
<hr>
|
<h2>Costum Stylesheet</h2>
|
||||||
|
|
||||||
<h2>CSS</h2>
|
|
||||||
|
|
||||||
<p class="small">Costum user CSS</p>
|
|
||||||
|
|
||||||
<textarea name="css"></textarea>
|
<textarea name="css"></textarea>
|
||||||
</div>
|
|
||||||
|
<p><input type="submit" value="Save"></p>
|
||||||
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -62,116 +57,153 @@ export default {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
|
id: 0,
|
||||||
admin: false,
|
admin: false,
|
||||||
allowNew: false,
|
allowNew: false,
|
||||||
allowEdit: false,
|
allowEdit: false,
|
||||||
allowCommands: false,
|
allowCommands: false,
|
||||||
passwordBlock: true,
|
|
||||||
password: '',
|
password: '',
|
||||||
username: '',
|
username: '',
|
||||||
scope: '',
|
filesystem: '',
|
||||||
rules: '',
|
rules: '',
|
||||||
css: '',
|
css: '',
|
||||||
commands: ''
|
commands: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
passwordPlaceholder () {
|
||||||
|
if (this.$route.path === '/users/new') return ''
|
||||||
|
return '(leave blank to avoid changes)'
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
if (this.$route.path === '/users/new') return
|
this.fetchData()
|
||||||
|
|
||||||
api.getUser(this.$route.params[0]).then(user => {
|
|
||||||
this.admin = user.admin
|
|
||||||
this.allowCommands = user.allowCommands
|
|
||||||
this.allowNew = user.allowNew
|
|
||||||
this.allowEdit = user.allowEdit
|
|
||||||
this.scope = user.filesystem
|
|
||||||
this.username = user.username
|
|
||||||
this.commands = user.commands.join(' ')
|
|
||||||
this.css = user.css
|
|
||||||
|
|
||||||
for (let rule of user.rules) {
|
|
||||||
if (rule.allow) {
|
|
||||||
this.rules += 'allow '
|
|
||||||
} else {
|
|
||||||
this.rules += 'disallow '
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.regex) {
|
|
||||||
this.rules += 'regex ' + rule.regexp.raw
|
|
||||||
} else {
|
|
||||||
this.rules += rule.path
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rules += '\n'
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
'$route': 'fetchData',
|
||||||
admin: function () {
|
admin: function () {
|
||||||
if (!this.admin) return
|
if (!this.admin) return
|
||||||
this.allowCommands = true
|
this.allowCommands = true
|
||||||
this.allowEdit = true
|
this.allowEdit = true
|
||||||
this.allowNew = true
|
this.allowNew = true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData () {
|
||||||
|
if (this.$route.path === '/users/new') return
|
||||||
|
|
||||||
|
api.getUser(this.$route.params[0]).then(user => {
|
||||||
|
this.id = user.ID
|
||||||
|
this.admin = user.admin
|
||||||
|
this.allowCommands = user.allowCommands
|
||||||
|
this.allowNew = user.allowNew
|
||||||
|
this.allowEdit = user.allowEdit
|
||||||
|
this.filesystem = user.filesystem
|
||||||
|
this.username = user.username
|
||||||
|
this.commands = user.commands.join(' ')
|
||||||
|
this.css = user.css
|
||||||
|
|
||||||
|
for (let rule of user.rules) {
|
||||||
|
if (rule.allow) {
|
||||||
|
this.rules += 'allow '
|
||||||
|
} else {
|
||||||
|
this.rules += 'disallow '
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.regex) {
|
||||||
|
this.rules += 'regex ' + rule.regexp.raw
|
||||||
|
} else {
|
||||||
|
this.rules += rule.path
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rules += '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rules = this.rules.trim()
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
this.$router.push({ path: '/users/new' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
save (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
let user = this.parseForm()
|
||||||
|
|
||||||
|
if (this.$route.path === '/users/new') {
|
||||||
|
api.newUser(user).then(location => {
|
||||||
|
this.$router.push({ path: location })
|
||||||
|
}).catch(e => {
|
||||||
|
this.$store.commit('showError', e)
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.updateUser(user).then(location => {
|
||||||
|
this.$router.push({ path: location })
|
||||||
|
}).catch(e => {
|
||||||
|
this.$store.commit('showError', e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
parseForm () {
|
||||||
|
let user = {
|
||||||
|
ID: this.id,
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
filesystem: this.filesystem,
|
||||||
|
admin: this.admin,
|
||||||
|
allowCommands: this.allowCommands,
|
||||||
|
allowNew: this.allowNew,
|
||||||
|
allowEdit: this.allowEdit,
|
||||||
|
css: this.css,
|
||||||
|
commands: this.commands.split(' '),
|
||||||
|
rules: []
|
||||||
|
}
|
||||||
|
|
||||||
|
let rules = this.rules.split('\n')
|
||||||
|
|
||||||
|
for (let rawRule of rules) {
|
||||||
|
let rule = {
|
||||||
|
allow: true,
|
||||||
|
path: '',
|
||||||
|
regex: false,
|
||||||
|
regexp: {
|
||||||
|
raw: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawRule = rawRule.split(' ')
|
||||||
|
|
||||||
|
// Skip a malformed rule
|
||||||
|
if (rawRule.length < 2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip a malformed rule
|
||||||
|
if (rawRule[0] !== 'allow' && rawRule[0] !== 'disallow') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.allow = (rawRule[0] === 'allow')
|
||||||
|
rawRule.shift()
|
||||||
|
|
||||||
|
if (rawRule[0] === 'regex') {
|
||||||
|
rule.regex = true
|
||||||
|
rawRule.shift()
|
||||||
|
rule.regexp.raw = rawRule.join(' ')
|
||||||
|
} else {
|
||||||
|
rule.path = rawRule.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
user.rules.push(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dashboard {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard textarea,
|
|
||||||
.dashboard input[type="text"],
|
|
||||||
.dashboard input[type="password"] {
|
|
||||||
padding: .5em 1em;
|
|
||||||
display: block;
|
|
||||||
border: 1px solid #e9e9e9;
|
|
||||||
transition: .2s ease border;
|
|
||||||
color: #333;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard textarea:focus,
|
|
||||||
.dashboard textarea:hover,
|
|
||||||
.dashboard input[type="text"]:focus,
|
|
||||||
.dashboard input[type="password"]:focus,
|
|
||||||
.dashboard input[type="text"]:hover,
|
|
||||||
.dashboard input[type="password"]:hover {
|
|
||||||
border-color: #9f9f9f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard textarea {
|
|
||||||
font-family: monospace;
|
|
||||||
min-height: 10em;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard p label {
|
|
||||||
margin-bottom: .2em;
|
|
||||||
display: block;
|
|
||||||
font-size: .8em
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border-bottom: 2px solid rgba(181, 181, 181, 0.5);
|
|
||||||
border-top: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-left: 0;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li code,
|
|
||||||
p code {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
padding: .1em;
|
|
||||||
border-radius: .2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small {
|
|
||||||
font-size: .8em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,11 +1,43 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="dashboard">
|
||||||
<h1>Users</h1>
|
<h1>Users <router-link to="/users/new"><button>New</button></router-link></h1>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Admin</th>
|
||||||
|
<th>Scope</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="user in users">
|
||||||
|
<td>{{ user.username }}</td>
|
||||||
|
<td><i v-if="user.admin" class="material-icons">done</i><i v-else class="material-icons">close</i></td>
|
||||||
|
<td>{{ user.filesystem }}</td>
|
||||||
|
<td><router-link :to="'/users/' + user.ID"><i class="material-icons">mode_edit</i></router-link></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import api from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'users'
|
name: 'users',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
api.getUsers().then(users => {
|
||||||
|
this.users = users
|
||||||
|
console.log(users)
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@ body {
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: #333;
|
color: #212121;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
.dashboard {
|
||||||
|
max-width: 600px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
|
border-radius: .5em;
|
||||||
|
background: #fff;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard a {
|
||||||
|
color: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard h1 button {
|
||||||
|
font-size: 0.5em;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table tr {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table th {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #757575;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table th,
|
||||||
|
.dashboard table td {
|
||||||
|
padding: .5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table td {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard table td:last-child {
|
||||||
|
width: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard > *:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.dashboard input[type="submit"],
|
||||||
|
.dashboard form input[type="submit"] {
|
||||||
|
margin-left: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea,
|
||||||
|
.dashboard input[type="text"],
|
||||||
|
.dashboard input[type="password"] {
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.7;
|
||||||
|
display: block;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
transition: .2s ease border;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard #username,
|
||||||
|
.dashboard #password,
|
||||||
|
.dashboard #scope {
|
||||||
|
max-width: 18em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea:focus,
|
||||||
|
.dashboard textarea:hover,
|
||||||
|
.dashboard input[type="text"]:focus,
|
||||||
|
.dashboard input[type="password"]:focus,
|
||||||
|
.dashboard input[type="text"]:hover,
|
||||||
|
.dashboard input[type="password"]:hover {
|
||||||
|
border-color: #2979ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard input.red {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard input.green {
|
||||||
|
border-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard textarea {
|
||||||
|
line-height: 1.15;
|
||||||
|
padding: .5em;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-family: monospace;
|
||||||
|
min-height: 10em;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard p label {
|
||||||
|
margin-bottom: .2em;
|
||||||
|
display: block;
|
||||||
|
font-size: .8em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
li code,
|
||||||
|
p code {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: .8em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor .CodeMirror {
|
#editor .CodeMirror {
|
||||||
border: 1px solid #dddddd;
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
margin: 2em 0;
|
margin: 2em 0;
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
@import "./prompts.css";
|
@import "./prompts.css";
|
||||||
@import "./listing.css";
|
@import "./listing.css";
|
||||||
@import "./editor.css";
|
@import "./editor.css";
|
||||||
|
@import "./dashboard.css";
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * *
|
||||||
* ACTION *
|
* ACTION *
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Users from '@/components/Users'
|
||||||
import User from '@/components/User'
|
import User from '@/components/User'
|
||||||
import Settings from '@/components/Settings'
|
import Settings from '@/components/Settings'
|
||||||
import auth from '@/utils/auth.js'
|
import auth from '@/utils/auth.js'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
|
|
||||||
|
@ -55,7 +56,10 @@ const router = new Router({
|
||||||
{
|
{
|
||||||
path: '/users',
|
path: '/users',
|
||||||
name: 'Users',
|
name: 'Users',
|
||||||
component: Users
|
component: Users,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/users/',
|
path: '/users/',
|
||||||
|
@ -66,7 +70,10 @@ const router = new Router({
|
||||||
{
|
{
|
||||||
path: '/users/*',
|
path: '/users/*',
|
||||||
name: 'User',
|
name: 'User',
|
||||||
component: User
|
component: User,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/*',
|
path: '/*',
|
||||||
|
@ -85,6 +92,19 @@ router.beforeEach((to, from, next) => {
|
||||||
// if not, redirect to login page.
|
// if not, redirect to login page.
|
||||||
auth.loggedIn()
|
auth.loggedIn()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
if (to.matched.some(record => record.meta.requiresAdmin)) {
|
||||||
|
if (store.state.user.admin) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next({
|
||||||
|
path: '/403'
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
|
|
@ -190,6 +190,27 @@ function download (format, ...files) {
|
||||||
window.open(url)
|
window.open(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUsers () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('GET', `${store.state.baseURL}/api/users/`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 200:
|
||||||
|
resolve(JSON.parse(request.responseText))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getUser (id) {
|
function getUser (id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
|
@ -211,6 +232,90 @@ function getUser (id) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newUser (user) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('POST', `${store.state.baseURL}/api/users/`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 201:
|
||||||
|
resolve(request.getResponseHeader('Location'))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send(JSON.stringify(user))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('PUT', `${store.state.baseURL}/api/users/${user.ID}`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 200:
|
||||||
|
resolve(request.getResponseHeader('Location'))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send(JSON.stringify(user))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePassword (password) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('PUT', `${store.state.baseURL}/api/users/self`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 200:
|
||||||
|
resolve()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send(JSON.stringify({ 'password': password }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCSS (css) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let request = new window.XMLHttpRequest()
|
||||||
|
request.open('PUT', `${store.state.baseURL}/api/users/self`, true)
|
||||||
|
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
|
||||||
|
|
||||||
|
request.onload = () => {
|
||||||
|
switch (request.status) {
|
||||||
|
case 200:
|
||||||
|
resolve()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
reject(request.responseText)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.onerror = (error) => reject(error)
|
||||||
|
request.send(JSON.stringify({ 'css': css }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
delete: rm,
|
delete: rm,
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -221,5 +326,10 @@ export default {
|
||||||
command,
|
command,
|
||||||
search,
|
search,
|
||||||
download,
|
download,
|
||||||
getUser
|
getUser,
|
||||||
|
newUser,
|
||||||
|
updateUser,
|
||||||
|
getUsers,
|
||||||
|
updatePassword,
|
||||||
|
updateCSS
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue