From af369cdea115cf56e4f10d39fd75ca4455ff0145 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 7 Jul 2017 20:00:32 +0100 Subject: [PATCH] Dashboard updates --- api.go | 53 +++++-- assets/src/components/Settings.vue | 72 ++++++++- assets/src/components/User.vue | 238 ++++++++++++++++------------- assets/src/components/Users.vue | 38 ++++- assets/src/css/base.css | 2 +- assets/src/css/dashboard.css | 121 +++++++++++++++ assets/src/css/editor.css | 2 +- assets/src/css/styles.css | 1 + assets/src/router/index.js | 24 ++- assets/src/utils/api.js | 112 +++++++++++++- 10 files changed, 536 insertions(+), 127 deletions(-) create mode 100644 assets/src/css/dashboard.css diff --git a/api.go b/api.go index 0bf2948e..134d2f9e 100644 --- a/api.go +++ b/api.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "sort" "strconv" "strings" @@ -361,6 +362,10 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) users = append(users, u) } + sort.Slice(users, func(i, j int) bool { + return users[i].ID < users[j].ID + }) + return renderJSON(w, users) } @@ -449,8 +454,8 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request) c.fm.Users[u.Username] = &u // Set the Location header and return. + w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID)) w.WriteHeader(http.StatusCreated) - w.Header().Set("Location", c.fm.RootURL()+"/api/users/"+strconv.Itoa(u.ID)) return 0, nil } @@ -516,22 +521,32 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) } if sid == "self" { - if u.Password == "" { - return http.StatusBadRequest, errors.New("Password missing") + if u.Password != "" { + 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 err != nil { - return http.StatusInternalServerError, err + if u.CSS != "" { + c.us.CSS = u.CSS + 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 - err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "Password", pw) - if err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusOK, nil + return http.StatusBadRequest, errors.New("Password or CSS is missing") } // 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.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 // to send a new entire object. diff --git a/assets/src/components/Settings.vue b/assets/src/components/Settings.vue index 1229ddab..e6e87667 100644 --- a/assets/src/components/Settings.vue +++ b/assets/src/components/Settings.vue @@ -1,11 +1,79 @@ diff --git a/assets/src/components/User.vue b/assets/src/components/User.vue index 43b971e6..43d3bbca 100644 --- a/assets/src/components/User.vue +++ b/assets/src/components/User.vue @@ -1,12 +1,11 @@ diff --git a/assets/src/components/Users.vue b/assets/src/components/Users.vue index b6c01803..a94f81ef 100644 --- a/assets/src/components/Users.vue +++ b/assets/src/components/Users.vue @@ -1,11 +1,43 @@ diff --git a/assets/src/css/base.css b/assets/src/css/base.css index 898ece28..207fa0b7 100644 --- a/assets/src/css/base.css +++ b/assets/src/css/base.css @@ -3,7 +3,7 @@ body { padding-top: 4em; background-color: #f8f8f8; user-select: none; - color: #333; + color: #212121; } * { diff --git a/assets/src/css/dashboard.css b/assets/src/css/dashboard.css new file mode 100644 index 00000000..0c6703d3 --- /dev/null +++ b/assets/src/css/dashboard.css @@ -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; +} diff --git a/assets/src/css/editor.css b/assets/src/css/editor.css index 0c25a2c4..971bf423 100644 --- a/assets/src/css/editor.css +++ b/assets/src/css/editor.css @@ -6,7 +6,7 @@ } #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; border-radius: .5em; } diff --git a/assets/src/css/styles.css b/assets/src/css/styles.css index e552a501..3f654e0c 100644 --- a/assets/src/css/styles.css +++ b/assets/src/css/styles.css @@ -5,6 +5,7 @@ @import "./prompts.css"; @import "./listing.css"; @import "./editor.css"; +@import "./dashboard.css"; /* * * * * * * * * * * * * * * * * ACTION * diff --git a/assets/src/router/index.js b/assets/src/router/index.js index 16a6b01d..08267aa3 100644 --- a/assets/src/router/index.js +++ b/assets/src/router/index.js @@ -7,6 +7,7 @@ import Users from '@/components/Users' import User from '@/components/User' import Settings from '@/components/Settings' import auth from '@/utils/auth.js' +import store from '@/store' Vue.use(Router) @@ -55,7 +56,10 @@ const router = new Router({ { path: '/users', name: 'Users', - component: Users + component: Users, + meta: { + requiresAdmin: true + } }, { path: '/users/', @@ -66,7 +70,10 @@ const router = new Router({ { path: '/users/*', name: 'User', - component: User + component: User, + meta: { + requiresAdmin: true + } }, { path: '/*', @@ -85,6 +92,19 @@ router.beforeEach((to, from, next) => { // if not, redirect to login page. auth.loggedIn() .then(() => { + if (to.matched.some(record => record.meta.requiresAdmin)) { + if (store.state.user.admin) { + next() + return + } + + next({ + path: '/403' + }) + + return + } + next() }) .catch(e => { diff --git a/assets/src/utils/api.js b/assets/src/utils/api.js index d76f1397..f7132f3f 100644 --- a/assets/src/utils/api.js +++ b/assets/src/utils/api.js @@ -190,6 +190,27 @@ function download (format, ...files) { 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) { return new Promise((resolve, reject) => { 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 { delete: rm, fetch, @@ -221,5 +326,10 @@ export default { command, search, download, - getUser + getUser, + newUser, + updateUser, + getUsers, + updatePassword, + updateCSS }