- Added Admin/Site config page in frontend for admin users
- Added API POST/GET /config endpoint
This commit is contained in:
Daniel Mason 2021-03-31 21:40:20 +13:00
parent 8be4a190a6
commit af02bd99cc
Signed by: idanoo
GPG Key ID: 387387CDBC02F132
17 changed files with 826 additions and 24 deletions

View File

@ -3,12 +3,13 @@ stages:
- bundle - bundle
variables: variables:
VERSION: 0.0.7 VERSION: 0.0.8
build-go: build-go:
image: golang:1.16.2 image: golang:1.16.2
stage: build stage: build
only: master only:
- master
script: script:
- go build -o goscrobble cmd/go-scrobble/*.go - go build -o goscrobble cmd/go-scrobble/*.go
artifacts: artifacts:
@ -22,7 +23,8 @@ build-go:
build-react: build-react:
image: node:15.12.0 image: node:15.12.0
stage: build stage: build
only: master only:
- master
script: script:
- cd web - cd web
- npm install - npm install
@ -35,7 +37,8 @@ build-react:
bundle: bundle:
image: bash:latest image: bash:latest
stage: bundle stage: bundle
only: master only:
- master
variables: variables:
GIT_STRATEGY: none GIT_STRATEGY: none
before_script: before_script:

View File

@ -1,8 +1,11 @@
# 0.0.8
- Added Admin/Site config page in frontend for admin users
- Added API POST/GET /config endpoint
# 0.0.7 # 0.0.7
- Switch redux -> Context - Switch redux -> Context
- Remove excess packages - Remove excess packages
# 0.0.6 # 0.0.6
- Fix hitting dashboard when logged out - Fix hitting dashboard when logged out
- Clean up app.js - Clean up app.js

View File

@ -0,0 +1,57 @@
package goscrobble
import (
"errors"
"fmt"
"log"
)
type Config struct {
Setting map[string]string `json:"configs"`
}
func getAllConfigs() (Config, error) {
config := Config{}
configs := make(map[string]string)
rows, err := db.Query("SELECT `key`, `value` FROM `config`")
if err != nil {
log.Printf("Failed to fetch config: %+v", err)
return config, errors.New("Failed to fetch configs")
}
defer rows.Close()
for rows.Next() {
var key string
var value string
err := rows.Scan(&key, &value)
if err != nil {
log.Printf("Failed to fetch config: %+v", err)
return config, errors.New("Failed to fetch configs")
}
// Append
configs[key] = value
}
// Assign the data to the parent
config.Setting = configs
err = rows.Err()
if err != nil {
log.Printf("Failed to fetch config: %+v", err)
return config, errors.New("Failed to fetch configs")
}
return config, nil
}
func updateConfigValue(key string, value string) error {
_, err := db.Exec("UPDATE `config` SET `value` = ? WHERE `key` = ?", value, key)
if err != nil {
fmt.Printf("Failed to update config: %+v", err)
return errors.New("Failed to update config value.")
}
return nil
}

View File

@ -15,6 +15,7 @@ var JwtExpiry time.Duration
type CustomClaims struct { type CustomClaims struct {
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` Email string `json:"email"`
Admin bool `json:"admin"`
jwt.StandardClaims jwt.StandardClaims
} }
@ -23,6 +24,7 @@ func generateJWTToken(user User) (string, error) {
atClaims["sub"] = user.UUID atClaims["sub"] = user.UUID
atClaims["username"] = user.Username atClaims["username"] = user.Username
atClaims["email"] = user.Email atClaims["email"] = user.Email
atClaims["admin"] = user.Admin
atClaims["iat"] = time.Now().Unix() atClaims["iat"] = time.Now().Unix()
atClaims["exp"] = time.Now().Add(JwtExpiry).Unix() atClaims["exp"] = time.Now().Add(JwtExpiry).Unix()
at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims) at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims)

View File

@ -51,6 +51,10 @@ func HandleRequests(port string) {
// JWT Auth // JWT Auth
v1.HandleFunc("/user/{id}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET") v1.HandleFunc("/user/{id}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET")
// Config auth
v1.HandleFunc("/config", adminMiddleware(fetchConfig)).Methods("GET")
v1.HandleFunc("/config", adminMiddleware(postConfig)).Methods("POST")
// No Auth // No Auth
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST") v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST") v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
@ -117,6 +121,16 @@ func throwOkMessage(w http.ResponseWriter, m string) {
w.Write(js) w.Write(js)
} }
// throwOkMessage - Throws a happy 200
func throwInvalidJson(w http.ResponseWriter) {
jr := jsonResponse{
Err: "Invalid JSON",
}
js, _ := json.Marshal(&jr)
w.WriteHeader(http.StatusBadRequest)
w.Write(js)
}
// generateJsonMessage - Generates a message:str response // generateJsonMessage - Generates a message:str response
func generateJsonMessage(m string) []byte { func generateJsonMessage(m string) []byte {
jr := jsonResponse{ jr := jsonResponse{
@ -142,9 +156,10 @@ func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http
authToken := strings.Replace(fullToken, "Bearer ", "", 1) authToken := strings.Replace(fullToken, "Bearer ", "", 1)
if authToken == "" { if authToken == "" {
throwUnauthorized(w, "A token is required") throwUnauthorized(w, "A token is required")
return
} }
userUuid, err := getUserForToken(authToken) userUuid, err := getUserUuidForToken(authToken)
if err != nil { if err != nil {
throwUnauthorized(w, err.Error()) throwUnauthorized(w, err.Error())
return return
@ -180,6 +195,32 @@ func jwtMiddleware(next func(http.ResponseWriter, *http.Request, string, string)
} }
} }
// adminMiddleware - Validates user is admin
func adminMiddleware(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fullToken := r.Header.Get("Authorization")
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
claims, err := verifyJWTToken(authToken)
if err != nil {
throwUnauthorized(w, "Invalid JWT Token")
return
}
user, err := getUser(claims.Subject)
if err != nil {
throwUnauthorized(w, err.Error())
return
}
if !user.Admin {
throwUnauthorized(w, "User is not admin")
return
}
next(w, r, claims.Subject)
}
}
// limitMiddleware - Rate limits important stuff // limitMiddleware - Rate limits important stuff
func limitMiddleware(next http.HandlerFunc, limiter *IPRateLimiter) http.HandlerFunc { func limitMiddleware(next http.HandlerFunc, limiter *IPRateLimiter) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -255,8 +296,7 @@ func handleStats(w http.ResponseWriter, r *http.Request) {
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) { func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
bodyJson, err := decodeJson(r.Body) bodyJson, err := decodeJson(r.Body)
if err != nil { if err != nil {
// If we can't decode. Lets tell them nicely. throwInvalidJson(w)
http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest)
return return
} }
@ -302,6 +342,38 @@ func fetchScrobbleResponse(w http.ResponseWriter, r *http.Request, jwtUser strin
w.Write(json) w.Write(json)
} }
// fetchScrobbles - Return an array of scrobbles
func fetchConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
config, err := getAllConfigs()
if err != nil {
throwOkError(w, "Failed to fetch scrobbles")
return
}
json, _ := json.Marshal(&config)
w.WriteHeader(http.StatusOK)
w.Write(json)
}
// fetchScrobbles - Return an array of scrobbles
func postConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
bodyJson, err := decodeJson(r.Body)
if err != nil {
throwInvalidJson(w)
return
}
for k, v := range bodyJson {
err = updateConfigValue(k, fmt.Sprintf("%s", v))
if err != nil {
throwOkError(w, err.Error())
return
}
}
throwOkMessage(w, "Config updated successfully")
}
// FRONTEND HANDLING // FRONTEND HANDLING
// ServerHTTP - Frontend server // ServerHTTP - Frontend server

View File

@ -15,7 +15,7 @@ func generateToken(n int) string {
return string(b) return string(b)
} }
func getUserForToken(token string) (string, error) { func getUserUuidForToken(token string) (string, error) {
var uuid string var uuid string
cachedKey := getRedisVal("user_token:" + token) cachedKey := getRedisVal("user_token:" + token)
if cachedKey == "" { if cachedKey == "" {

View File

@ -98,16 +98,16 @@ func loginUser(logReq *LoginRequest, ip net.IP) ([]byte, error) {
} }
if strings.Contains(logReq.Username, "@") { if strings.Contains(logReq.Username, "@") {
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `email` = ? AND `active` = 1", err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `email` = ? AND `active` = 1",
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password) logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return resp, errors.New("Invalid Username or Password") return resp, errors.New("Invalid Username or Password")
} }
} }
} else { } else {
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password` FROM `users` WHERE `username` = ? AND `active` = 1", err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `username` = ? AND `active` = 1",
logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password) logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return resp, errors.New("Invalid Username or Password") return resp, errors.New("Invalid Username or Password")
} }
@ -193,3 +193,15 @@ func userAlreadyExists(req *RegisterRequest) bool {
return count > 0 return count > 0
} }
func getUser(uuid string) (User, error) {
var user User
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin` FROM `users` WHERE `uuid` = UUID_TO_BIN(?, true) AND `active` = 1",
uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin)
if err == sql.ErrNoRows {
return user, errors.New("Invalid JWT Token")
}
return user, nil
}

View File

@ -0,0 +1 @@
TRUNCATE `config`;

View File

@ -0,0 +1,7 @@
INSERT INTO
`config`(`key`, `value`)
VALUES
('SPOTIFY_API_ID', ''),
('SPOTIFY_API_SECRET', ''),
('LASTFM_API_KEY', ''),
('REGISTRATION_ENABLED', '1');

474
web/package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "goscrobble", "name": "goscrobble",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.3",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
@ -15,6 +16,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap": "^4.6.0", "bootstrap": "^4.6.0",
"formik": "^2.2.6", "formik": "^2.2.6",
"formik-material-ui": "^3.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
@ -2370,6 +2372,155 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@material-ui/core": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz",
"integrity": "sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw==",
"dependencies": {
"@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.11.3",
"@material-ui/system": "^4.11.3",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.4",
"hoist-non-react-statics": "^3.3.2",
"popper.js": "1.16.1-lts",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0",
"react-transition-group": "^4.4.0"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/material-ui"
},
"peerDependencies": {
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@material-ui/core/node_modules/popper.js": {
"version": "1.16.1-lts",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
"integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
},
"node_modules/@material-ui/styles": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.3.tgz",
"integrity": "sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg==",
"dependencies": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"clsx": "^1.0.4",
"csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2",
"jss": "^10.5.1",
"jss-plugin-camel-case": "^10.5.1",
"jss-plugin-default-unit": "^10.5.1",
"jss-plugin-global": "^10.5.1",
"jss-plugin-nested": "^10.5.1",
"jss-plugin-props-sort": "^10.5.1",
"jss-plugin-rule-value-function": "^10.5.1",
"jss-plugin-vendor-prefixer": "^10.5.1",
"prop-types": "^15.7.2"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/material-ui"
},
"peerDependencies": {
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@material-ui/styles/node_modules/csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
},
"node_modules/@material-ui/system": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz",
"integrity": "sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==",
"dependencies": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2",
"csstype": "^2.5.2",
"prop-types": "^15.7.2"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/material-ui"
},
"peerDependencies": {
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@material-ui/system/node_modules/csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
},
"node_modules/@material-ui/types": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
"integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
"peerDependencies": {
"@types/react": "*"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@material-ui/utils": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz",
"integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==",
"dependencies": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
},
"engines": {
"node": ">=8.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@ -6186,6 +6337,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/css-vendor": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
"integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
"dependencies": {
"@babel/runtime": "^7.8.3",
"is-in-browser": "^1.0.2"
}
},
"node_modules/css-what": { "node_modules/css-what": {
"version": "3.4.2", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@ -9009,6 +9169,17 @@
"react": ">=16.8.0" "react": ">=16.8.0"
} }
}, },
"node_modules/formik-material-ui": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/formik-material-ui/-/formik-material-ui-3.0.1.tgz",
"integrity": "sha512-N8oxZIdhY70npRv86IfF6Zaaps9RL3a37XRdq02WDroB3XZC1mXs6lA/zQ09ZYFWYJp/UjI80SKVpVa/xJOJJA==",
"peerDependencies": {
"@material-ui/core": ">=4.0.0",
"formik": ">=2.0.0",
"react": ">=16.8.0",
"tiny-warning": ">=1.0.2"
}
},
"node_modules/formik/node_modules/deepmerge": { "node_modules/formik/node_modules/deepmerge": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
@ -9954,6 +10125,11 @@
"node": ">=8.12.0" "node": ">=8.12.0"
} }
}, },
"node_modules/hyphenate-style-name": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -10115,6 +10291,14 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/indefinite-observable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz",
"integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==",
"dependencies": {
"symbol-observable": "1.2.0"
}
},
"node_modules/indent-string": { "node_modules/indent-string": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -10432,6 +10616,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"node_modules/is-module": { "node_modules/is-module": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
@ -12845,6 +13034,89 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"node_modules/jss": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz",
"integrity": "sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"csstype": "^3.0.2",
"indefinite-observable": "^2.0.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/jss"
}
},
"node_modules/jss-plugin-camel-case": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz",
"integrity": "sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.6.0"
}
},
"node_modules/jss-plugin-default-unit": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz",
"integrity": "sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"node_modules/jss-plugin-global": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz",
"integrity": "sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"node_modules/jss-plugin-nested": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz",
"integrity": "sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0",
"tiny-warning": "^1.0.2"
}
},
"node_modules/jss-plugin-props-sort": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz",
"integrity": "sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"node_modules/jss-plugin-rule-value-function": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz",
"integrity": "sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0",
"tiny-warning": "^1.0.2"
}
},
"node_modules/jss-plugin-vendor-prefixer": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz",
"integrity": "sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.8",
"jss": "10.6.0"
}
},
"node_modules/jsx-ast-utils": { "node_modules/jsx-ast-utils": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@ -24050,6 +24322,96 @@
} }
} }
}, },
"@material-ui/core": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz",
"integrity": "sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.11.3",
"@material-ui/system": "^4.11.3",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.4",
"hoist-non-react-statics": "^3.3.2",
"popper.js": "1.16.1-lts",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0",
"react-transition-group": "^4.4.0"
},
"dependencies": {
"popper.js": {
"version": "1.16.1-lts",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
"integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
}
}
},
"@material-ui/styles": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.3.tgz",
"integrity": "sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg==",
"requires": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"clsx": "^1.0.4",
"csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2",
"jss": "^10.5.1",
"jss-plugin-camel-case": "^10.5.1",
"jss-plugin-default-unit": "^10.5.1",
"jss-plugin-global": "^10.5.1",
"jss-plugin-nested": "^10.5.1",
"jss-plugin-props-sort": "^10.5.1",
"jss-plugin-rule-value-function": "^10.5.1",
"jss-plugin-vendor-prefixer": "^10.5.1",
"prop-types": "^15.7.2"
},
"dependencies": {
"csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
}
}
},
"@material-ui/system": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz",
"integrity": "sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2",
"csstype": "^2.5.2",
"prop-types": "^15.7.2"
},
"dependencies": {
"csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
}
}
},
"@material-ui/types": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
"integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
"requires": {}
},
"@material-ui/utils": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz",
"integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==",
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@ -27073,6 +27435,15 @@
} }
} }
}, },
"css-vendor": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
"integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
"requires": {
"@babel/runtime": "^7.8.3",
"is-in-browser": "^1.0.2"
}
},
"css-what": { "css-what": {
"version": "3.4.2", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
@ -29281,6 +29652,12 @@
} }
} }
}, },
"formik-material-ui": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/formik-material-ui/-/formik-material-ui-3.0.1.tgz",
"integrity": "sha512-N8oxZIdhY70npRv86IfF6Zaaps9RL3a37XRdq02WDroB3XZC1mXs6lA/zQ09ZYFWYJp/UjI80SKVpVa/xJOJJA==",
"requires": {}
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -30045,6 +30422,11 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
}, },
"hyphenate-style-name": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -30152,6 +30534,14 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
}, },
"indefinite-observable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz",
"integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==",
"requires": {
"symbol-observable": "1.2.0"
}
},
"indent-string": { "indent-string": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -30379,6 +30769,11 @@
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
} }
}, },
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"is-module": { "is-module": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
@ -32151,6 +32546,85 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jss": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz",
"integrity": "sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^3.0.2",
"indefinite-observable": "^2.0.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-camel-case": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz",
"integrity": "sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A==",
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.6.0"
}
},
"jss-plugin-default-unit": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz",
"integrity": "sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"jss-plugin-global": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz",
"integrity": "sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"jss-plugin-nested": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz",
"integrity": "sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-props-sort": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz",
"integrity": "sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0"
}
},
"jss-plugin-rule-value-function": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz",
"integrity": "sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.6.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz",
"integrity": "sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.8",
"jss": "10.6.0"
}
},
"jsx-ast-utils": { "jsx-ast-utils": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.3",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
@ -10,6 +11,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap": "^4.6.0", "bootstrap": "^4.6.0",
"formik": "^2.2.6", "formik": "^2.2.6",
"formik-material-ui": "^3.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",

View File

@ -25,6 +25,7 @@ export const PostLogin = (formValues) => {
uuid: expandedUser.sub, uuid: expandedUser.sub,
exp: expandedUser.exp, exp: expandedUser.exp,
username: expandedUser.username, username: expandedUser.username,
admin: expandedUser.admin
} }
toast.success('Successfully logged in.'); toast.success('Successfully logged in.');
@ -72,3 +73,31 @@ export const getRecentScrobbles = (id) => {
return data.data; return data.data;
}); });
}; };
export const getConfigs = () => {
return axios.get(process.env.REACT_APP_API_URL + "config", { headers: getHeaders() })
.then((data) => {
return data.data;
});
};
export const postConfigs = (values, toggle) => {
if (toggle) {
values.REGISTRATION_ENABLED = "1"
} else {
values.REGISTRATION_ENABLED = "0"
}
return axios.post(process.env.REACT_APP_API_URL + "config", values, { headers: getHeaders() })
.then((data) => {
if (data.data && data.data.message) {
toast.success(data.data.message);
} else if (data.data && data.data.error) {
toast.error(data.data.error);
}
})
.catch(() => {
toast.error('Error updating values');
});
};

View File

@ -1,16 +1,17 @@
import './App.css'; import { Route, Switch, withRouter } from 'react-router-dom';
import Home from './Pages/Home'; import Home from './Pages/Home';
import About from './Pages/About'; import About from './Pages/About';
import Dashboard from './Pages/Dashboard'; import Dashboard from './Pages/Dashboard';
import Profile from './Pages/Profile'; import Profile from './Pages/Profile';
import Admin from './Pages/Admin';
import Login from './Pages/Login'; import Login from './Pages/Login';
import Settings from './Pages/Settings';
import Register from './Pages/Register'; import Register from './Pages/Register';
import Navigation from './Components/Navigation'; import Navigation from './Components/Navigation';
import { Route, Switch, withRouter } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
const App = () => { const App = () => {
let boolTrue = true let boolTrue = true
@ -24,7 +25,8 @@ const App = () => {
<Route path="/dashboard" component={Dashboard} /> <Route path="/dashboard" component={Dashboard} />
<Route path="/profile" component={Profile} /> <Route path="/profile" component={Profile} />
<Route path="/settings" component={Settings} />
<Route path="/admin" component={Admin} />
<Route path="/login" component={Login} /> <Route path="/login" component={Login} />
<Route path="/register" component={Register} /> <Route path="/register" component={Register} />

View File

@ -33,10 +33,14 @@ const Navigation = () => {
let { user, Logout } = useContext(AuthContext); let { user, Logout } = useContext(AuthContext);
let [collapsed, setCollapsed] = useState(true); let [collapsed, setCollapsed] = useState(true);
const toggleCollapsed = () => {
setCollapsed(!collapsed)
}
const renderMobileNav = () => { const renderMobileNav = () => {
return <Navbar color="dark" dark fixed="top"> return <Navbar color="dark" dark fixed="top">
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand> <NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
<NavbarToggler onClick={setCollapsed(!collapsed)} className="mr-2" /> <NavbarToggler onClick={toggleCollapsed} className="mr-2" />
<Collapse isOpen={!collapsed} navbar> <Collapse isOpen={!collapsed} navbar>
{user ? {user ?
<Nav className="navLinkLoginMobile" navbar> <Nav className="navLinkLoginMobile" navbar>
@ -55,6 +59,12 @@ const Navigation = () => {
style={active === "profile" ? activeStyle : {}} style={active === "profile" ? activeStyle : {}}
className="navLinkMobile" className="navLinkMobile"
>Profile</Link> >Profile</Link>
{user.admin &&
<Link
to="/admin"
style={active === "admin" ? activeStyle : {}}
className="navLink"
>Admin</Link>}
<Link to="/" className="navLink" onClick={Logout}>Logout</Link> <Link to="/" className="navLink" onClick={Logout}>Logout</Link>
</Nav> </Nav>
: <Nav className="navLinkLoginMobile" navbar> : <Nav className="navLinkLoginMobile" navbar>
@ -126,7 +136,13 @@ const Navigation = () => {
style={active === "profile" ? activeStyle : {}} style={active === "profile" ? activeStyle : {}}
className="navLink" className="navLink"
>{user.username}</Link> >{user.username}</Link>
<Link to="/" className="navLink" onClick={Logout}>Logout</Link> {user.admin &&
<Link
to="/admin"
style={active === "admin" ? activeStyle : {}}
className="navLink"
>Admin</Link>}
<Link to="/admin" className="navLink" onClick={Logout}>Logout</Link>
</div> </div>
: :
<div className="navLinkLogin"> <div className="navLinkLogin">

15
web/src/Pages/Admin.css Normal file
View File

@ -0,0 +1,15 @@
.adminBody {
padding: 20px 5px 5px 5px;
font-size: 16pt;
width: 300px;
}
.adminFields {
width: 100%;
}
.admin {
height: 50px;
width: 100%;
margin-top:-5px;
}

107
web/src/Pages/Admin.js Normal file
View File

@ -0,0 +1,107 @@
import React, { useContext, useState, useEffect } from 'react';
import '../App.css';
import './Login.css';
import { Button } from 'reactstrap';
import { Formik, Form, Field } from 'formik';
import ScaleLoader from 'react-spinners/ScaleLoader';
import AuthContext from '../Contexts/AuthContext';
import { Switch } from 'formik-material-ui';
import { getConfigs, postConfigs } from '../Api/index'
const Admin = () => {
const { user } = useContext(AuthContext);
const [loading, setLoading] = useState(true);
const [configs, setConfigs] = useState({})
const [toggle, setToggle] = useState(false);
useEffect(() => {
getConfigs()
.then(data => {
setConfigs(data.configs);
setToggle(data.configs.REGISTRATION_ENABLED === "1")
setLoading(false);
})
}, [])
if (!user || !user.admin) {
return (
<div className="pageWrapper">
<h1>Unauthorized</h1>
</div>
)
}
if (loading) {
return (
<div className="pageWrapper">
<ScaleLoader color="#6AD7E5" />
</div>
)
}
const handleToggle = () => {
setToggle(!toggle);
};
return (
<div className="pageWrapper">
<h1>
Admin Panel
</h1>
<div className="loginBody">
<Formik
initialValues={configs}
onSubmit={(values) => postConfigs(values, toggle)}
>
<Form><br/>
<label>
<Field
type="checkbox"
name="REGISTRATION_ENABLED"
onChange={handleToggle}
component={Switch}
checked={toggle}
value={toggle}
/>
Registration Enabled
</label><br/><br/>
<label>
LastFM Api Key<br/>
<Field
name="LASTFM_API_KEY"
type="text"
className="loginFields"
/>
</label>
<br/>
<label>
Spotify App ID<br/>
<Field
name="SPOTIFY_APP_ID"
type="text"
className="loginFields"
/>
</label>
<label>
Spotify App Secret<br/>
<Field
name="SPOTIFY_APP_SECRET"
type="text"
className="loginFields"
/>
</label>
<br/><br/>
<Button
color="primary"
type="submit"
className="loginButton"
disabled={loading}
>Update</Button>
</Form>
</Formik>
</div>
</div>
);
}
export default Admin;

View File

@ -10,7 +10,7 @@ import AuthContext from '../Contexts/AuthContext';
const Dashboard = () => { const Dashboard = () => {
const history = useHistory(); const history = useHistory();
let { user } = useContext(AuthContext); let { user } = useContext(AuthContext);
let [isLoading, setIsLoading] = useState(true); let [loading, setLoading] = useState(true);
let [dashboardData, setDashboardData] = useState({}); let [dashboardData, setDashboardData] = useState({});
if (!user) { if (!user) {
@ -24,7 +24,7 @@ const Dashboard = () => {
getRecentScrobbles(user.uuid) getRecentScrobbles(user.uuid)
.then(data => { .then(data => {
setDashboardData(data); setDashboardData(data);
setIsLoading(false); setLoading(false);
}) })
}, [user]) }, [user])
@ -33,8 +33,8 @@ const Dashboard = () => {
<h1> <h1>
Dashboard! Dashboard!
</h1> </h1>
{isLoading {loading
? <ScaleLoader color="#FFF" size={60} /> ? <ScaleLoader color="#6AD7E5" size={60} />
: <ScrobbleTable data={dashboardData} /> : <ScrobbleTable data={dashboardData} />
} }
</div> </div>