diff --git a/package.json b/package.json
index ab243ef..5754776 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"react": "^15.3.2",
"react-bootstrap": "^0.30.6",
"react-bootstrap-sweetalert": "^3.0.0",
+ "react-bootstrap-toggle": "^2.0.8",
"react-dom": "^15.3.2",
"react-infinite-scroller": "^1.0.4",
"react-loading": "^0.0.9",
diff --git a/src/internal/admins/users.go b/src/internal/admins/users.go
index 3a7ade1..04947bd 100644
--- a/src/internal/admins/users.go
+++ b/src/internal/admins/users.go
@@ -1,8 +1,11 @@
package admin
import (
+ "encoding/json"
+ "fmt"
"net/http"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
@@ -24,3 +27,51 @@ func GetUsersHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error
return env.RenderJSON(w, users)
}
+
+// UpdateUserHandler updates the user data
+func UpdateUserHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
+ log := env.Log.WithFields(logrus.Fields{
+ "function": "admin.PostActivateUserHandler",
+ })
+
+ var data struct {
+ ID string `json:"userId"`
+ Admin bool `json:"admin"`
+ Activated bool `json:"activated"`
+ PolochonURL string `json:"polochonUrl"`
+ PolochonToken string `json:"polochonToken"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
+ return err
+ }
+
+ if data.ID == "" {
+ return env.RenderError(w, fmt.Errorf("Empty user id"))
+ }
+
+ user, err := users.GetByID(env.Database, data.ID)
+ if err != nil {
+ return err
+ }
+
+ // Update the polochon config
+ polochonConfig := &config.UserPolochon{
+ URL: data.PolochonURL,
+ Token: data.PolochonToken,
+ }
+ if err := user.SetConfig("polochon", polochonConfig); err != nil {
+ return err
+ }
+
+ user.Admin = data.Admin
+ user.Activated = data.Activated
+
+ log.Debugf("updating user")
+
+ // Save the user with the new configurations
+ if err := user.Update(env.Database); err != nil {
+ return err
+ }
+
+ return env.RenderOK(w, "user updated")
+}
diff --git a/src/internal/users/users.go b/src/internal/users/users.go
index d33e52d..90633de 100644
--- a/src/internal/users/users.go
+++ b/src/internal/users/users.go
@@ -1,6 +1,7 @@
package users
import (
+ "database/sql"
"encoding/json"
"errors"
"fmt"
@@ -15,17 +16,18 @@ import (
)
const (
- addUserQuery = `INSERT INTO users (name, hash, admin, rawconfig) VALUES ($1, $2, $3, $4) RETURNING id;`
- getUserQuery = `SELECT * FROM users WHERE name=$1;`
- updateUserQuery = `UPDATE users SET name=:name, hash=:hash, admin=:admin, rawconfig=:rawconfig WHERE id=:id RETURNING *;`
- deleteUseQuery = `DELETE FROM users WHERE id=:id;`
+ addUserQuery = `INSERT INTO users (name, hash, admin, rawconfig) VALUES ($1, $2, $3, $4) RETURNING id;`
+ getUserQuery = `SELECT * FROM users WHERE name=$1;`
+ getUserByIDQuery = `SELECT * FROM users WHERE id=$1;`
+ updateUserQuery = `UPDATE users SET name=:name, hash=:hash, admin=:admin, activated=:activated, rawconfig=:rawconfig WHERE id=:id RETURNING *;`
+ deleteUseQuery = `DELETE FROM users WHERE id=:id;`
addTokenQuery = `INSERT INTO tokens (value, user_id) VALUES ($1, $2) RETURNING id;`
getTokensQuery = `SELECT id, value FROM tokens WHERE user_id=$1;`
checkTokenQuery = `SELECT count(*) FROM tokens WHERE user_id=$1 AND value=$2;`
deleteTokenQuery = `DELETE FROM tokens WHERE user_id=$1 AND value=$2;`
- getAllUsersQuery = `SELECT * FROM users;`
+ getAllUsersQuery = `SELECT * FROM users order by created_at;`
)
const (
@@ -128,7 +130,20 @@ func Get(q sqlx.Queryer, name string) (*User, error) {
u := &User{}
err := q.QueryRowx(getUserQuery, name).StructScan(u)
if err != nil {
- if err.Error() == "sql: no rows in result set" {
+ if err == sql.ErrNoRows {
+ return nil, ErrUnknownUser
+ }
+ return nil, err
+ }
+ return u, nil
+}
+
+// GetByID returns user using its id
+func GetByID(q sqlx.Queryer, id string) (*User, error) {
+ u := &User{}
+ err := q.QueryRowx(getUserByIDQuery, id).StructScan(u)
+ if err != nil {
+ if err == sql.ErrNoRows {
return nil, ErrUnknownUser
}
return nil, err
diff --git a/src/public/js/actions/admins.js b/src/public/js/actions/admins.js
index 22759e1..15d2a65 100644
--- a/src/public/js/actions/admins.js
+++ b/src/public/js/actions/admins.js
@@ -6,3 +6,13 @@ export function getUsers() {
configureAxios().get("/admins/users")
)
}
+
+export function updateUser(data) {
+ return request(
+ "ADMIN_UPDATE_USER",
+ configureAxios().post("/admins/users", data),
+ [
+ () => getUsers(),
+ ]
+ )
+}
diff --git a/src/public/js/components/admins/panel.js b/src/public/js/components/admins/panel.js
index 6e1dc3d..cd1847d 100644
--- a/src/public/js/components/admins/panel.js
+++ b/src/public/js/components/admins/panel.js
@@ -1,7 +1,7 @@
import React from "react"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
-import { getUsers } from "../../actions/admins"
+import { updateUser } from "../../actions/admins"
import UserList from "./users"
@@ -11,11 +11,14 @@ function mapStateToProps(state) {
};
}
const mapDispatchToProps = (dipatch) =>
- bindActionCreators({ getUsers }, dipatch)
+ bindActionCreators({ updateUser }, dipatch)
function AdminPanel(props) {
return (
-
+
);
}
diff --git a/src/public/js/components/admins/users.js b/src/public/js/components/admins/users.js
index 47a482c..ace4667 100644
--- a/src/public/js/components/admins/users.js
+++ b/src/public/js/components/admins/users.js
@@ -1,5 +1,8 @@
import React from "react"
+import { Button, Modal } from "react-bootstrap"
+import Toggle from "react-bootstrap-toggle";
+
export default function UserList(props) {
return (
@@ -10,15 +13,21 @@ export default function UserList(props) {
# |
Name |
+ Activated |
Admin |
Polochon URL |
Polochon token |
+ Actions |
{props.users.map(function(el, index) {
return (
-
+
);
})}
@@ -31,15 +40,149 @@ function User(props) {
const polochonConfig = props.data.get("RawConfig").get("polochon");
const polochonURL = polochonConfig ? polochonConfig.get("url") : "-";
const polochonToken = polochonConfig ? polochonConfig.get("token") : "-";
- const admin = props.data.get("Admin") ? "yes" : "no";
return (
{props.data.get("id")} |
{props.data.get("Name")} |
- {admin} |
+ |
+ |
{polochonURL} |
{polochonToken} |
- |
+
+
+ |
);
}
+
+function UserAdminStatus(props) {
+ const admin = props.data.get("Admin");
+ const className = admin ? "fa fa-check" : "fa fa-times";
+ return (
);
+}
+
+function UserActivationStatus(props) {
+ const activated = props.data.get("Activated");
+ const className = activated ? "fa fa-check" : "fa fa-times text-danger";
+ return (
);
+}
+
+class UserEdit extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ // User props
+ const polochonConfig = props.data.get("RawConfig").get("polochon");
+ const polochonUrl = polochonConfig ? polochonConfig.get("url") : "";
+ const polochonToken = polochonConfig ? polochonConfig.get("token") : "";
+
+ // Functions
+ this.showModal = this.showModal.bind(this);
+ this.hideModal = this.hideModal.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleUrlInput = this.handleUrlInput.bind(this);
+ this.handleTokenInput = this.handleTokenInput.bind(this);
+ this.handleActivatedToggle = this.handleActivatedToggle.bind(this);
+ this.handleAdminToggle = this.handleAdminToggle.bind(this);
+
+ this.state = {
+ showModal: false,
+ polochonUrl: polochonUrl,
+ polochonToken: polochonToken,
+ activated: props.data.get("Activated"),
+ admin: props.data.get("Admin"),
+ };
+ }
+ showModal() {
+ this.setState({ showModal: true });
+ }
+ hideModal() {
+ this.setState({ showModal: false });
+ }
+ handleSubmit(e) {
+ if (e) { e.preventDefault(); }
+ this.props.updateUser({
+ userId: this.props.data.get("id"),
+ admin: this.state.admin,
+ activated: this.state.activated,
+ polochonUrl: this.state.polochonUrl,
+ polochonToken: this.state.polochonToken,
+ });
+ this.setState({ showModal: false });
+ }
+ handleTokenInput() {
+ this.setState({ polochonToken: this.refs.polochonToken.value });
+ }
+ handleUrlInput() {
+ this.setState({ polochonUrl: this.refs.polochonUrl.value });
+ }
+ handleActivatedToggle() {
+ this.setState({ activated: !this.state.activated });
+ }
+ handleAdminToggle() {
+ this.setState({ admin: !this.state.admin });
+ }
+ render() {
+ return (
+
+
+
+
+
+ Edit user - {this.props.data.get("Name")}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/public/less/app.less b/src/public/less/app.less
index 1f866ce..c6fbf3f 100644
--- a/src/public/less/app.less
+++ b/src/public/less/app.less
@@ -2,6 +2,7 @@
@import "~bootswatch/superhero/variables.less";
@import "~bootswatch/superhero/bootswatch.less";
@import "~font-awesome/less/font-awesome.less";
+@import "~react-bootstrap-toggle/src/bootstrap2-toggle.css";
body {
padding-top: @navbar-height + 10px;
@@ -75,3 +76,8 @@ div.sweet-alert > h2 {
.player-modal {
width: 90%;
}
+
+.admin-edit-user-modal {
+ margin-left: 3px;
+ margin-right: 3px;
+}
diff --git a/src/routes.go b/src/routes.go
index 6e2af31..f2d07df 100644
--- a/src/routes.go
+++ b/src/routes.go
@@ -60,4 +60,5 @@ func setupRoutes(env *web.Env) {
// Admin routes
env.Handle("/admins/users", admin.GetUsersHandler).WithRole(users.AdminRole).Methods("GET")
+ env.Handle("/admins/users", admin.UpdateUserHandler).WithRole(users.AdminRole).Methods("POST")
}
diff --git a/yarn.lock b/yarn.lock
index d3d2d2e..5b264f2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1125,6 +1125,14 @@ core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+create-react-class@^15.6.0:
+ version "15.6.0"
+ resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4"
+ dependencies:
+ fbjs "^0.8.9"
+ loose-envify "^1.3.1"
+ object-assign "^4.1.1"
+
cryptiles@2.x.x:
version "2.0.5"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
@@ -1613,6 +1621,18 @@ fbjs@^0.8.4:
promise "^7.1.1"
ua-parser-js "^0.7.9"
+fbjs@^0.8.9:
+ version "0.8.14"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.14.tgz#d1dbe2be254c35a91e09f31f9cd50a40b2a0ed1c"
+ dependencies:
+ core-js "^1.0.0"
+ isomorphic-fetch "^2.1.1"
+ loose-envify "^1.0.0"
+ object-assign "^4.1.0"
+ promise "^7.1.1"
+ setimmediate "^1.0.5"
+ ua-parser-js "^0.7.9"
+
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -2703,6 +2723,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0:
dependencies:
js-tokens "^2.0.0"
+loose-envify@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+ dependencies:
+ js-tokens "^3.0.0"
+
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -2971,6 +2997,10 @@ object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
object.omit@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
@@ -3406,6 +3436,13 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
+prop-types@^15.5.10:
+ version "15.5.10"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
+ dependencies:
+ fbjs "^0.8.9"
+ loose-envify "^1.3.1"
+
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@@ -3477,10 +3514,27 @@ react-bootstrap-sweetalert:
dependencies:
object-assign "^4.1.0"
+react-bootstrap-toggle@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/react-bootstrap-toggle/-/react-bootstrap-toggle-2.0.8.tgz#04e951527ffdd3b2a18de753b4992fa2a7d25792"
+ dependencies:
+ prop-types "^15.5.10"
+ react "^15.4.2"
+ react-dom "^15.4.2"
+
react-dom@^15.3.2:
version "15.3.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.3.2.tgz#c46b0aa5380d7b838e7a59c4a7beff2ed315531f"
+react-dom@^15.4.2:
+ version "15.6.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470"
+ dependencies:
+ fbjs "^0.8.9"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.0"
+ prop-types "^15.5.10"
+
react-infinite-scroller:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.0.4.tgz#cb171113c4c8ee6aa44669392a55ad50938e2028"
@@ -3539,6 +3593,16 @@ react@^15.3.2:
loose-envify "^1.1.0"
object-assign "^4.1.0"
+react@^15.4.2:
+ version "15.6.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
+ dependencies:
+ create-react-class "^15.6.0"
+ fbjs "^0.8.9"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.0"
+ prop-types "^15.5.10"
+
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -3845,6 +3909,10 @@ set-immediate-shim@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+setimmediate@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+
sha.js@2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"