From 2730dcdb17cb923c67af2b3844d23fefc6e43f3b Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Thu, 16 Mar 2017 11:35:57 +0100 Subject: [PATCH] Various ui improvements. --- internal/storage/application.go | 2 + ui/src/Layout.js | 9 +- ui/src/components/Navbar.js | 17 +++ ui/src/components/UserForm.js | 2 +- ui/src/index.js | 2 + ui/src/stores/ApplicationStore.js | 10 ++ ui/src/stores/SessionStore.js | 6 +- ui/src/views/applications/ApplicationUsers.js | 42 +----- .../applications/CreateApplicationUser.js | 26 +++- .../applications/UpdateApplicationUser.js | 137 ++++++++++++++++++ ui/src/views/users/CreateUser.js | 4 +- 11 files changed, 211 insertions(+), 46 deletions(-) create mode 100644 ui/src/views/applications/UpdateApplicationUser.js diff --git a/internal/storage/application.go b/internal/storage/application.go index e0f60224b..8852bf45e 100644 --- a/internal/storage/application.go +++ b/internal/storage/application.go @@ -375,6 +375,8 @@ func CreateUserForApplication(db *sqlx.DB, applicationID, userID int64, adminAcc switch err.Code.Name() { case "unique_violation": return ErrAlreadyExists + case "foreign_key_violation": + return ErrDoesNotExist default: return errors.Wrap(err, "insert error") } diff --git a/ui/src/Layout.js b/ui/src/Layout.js index b46f6ec64..b379c86bb 100644 --- a/ui/src/Layout.js +++ b/ui/src/Layout.js @@ -1,13 +1,20 @@ import React, { Component } from 'react'; import Navbar from "./components/Navbar"; import Errors from "./components/Errors"; +import dispatcher from "./dispatcher"; class Layout extends Component { + onClick() { + dispatcher.dispatch({ + type: "BODY_CLICK", + }); + } + render() { return (
-
+
{this.props.children} diff --git a/ui/src/components/Navbar.js b/ui/src/components/Navbar.js index 455aa6b46..97a87b000 100644 --- a/ui/src/components/Navbar.js +++ b/ui/src/components/Navbar.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; +import dispatcher from "../dispatcher"; import SessionStore from "../stores/SessionStore"; class Navbar extends Component { @@ -17,6 +18,7 @@ class Navbar extends Component { } this.toggleDropdown = this.toggleDropdown.bind(this); + this.handleActions = this.handleActions.bind(this); } toggleDropdown() { @@ -25,6 +27,19 @@ class Navbar extends Component { }); } + handleActions(action) { + switch(action.type) { + case "BODY_CLICK": { + this.setState({ + dropdownOpen: false, + }); + break; + } + default: + break; + } + } + componentWillMount() { SessionStore.on("change", () => { this.setState({ @@ -32,6 +47,8 @@ class Navbar extends Component { isAdmin: SessionStore.isAdmin(), }); }); + + dispatcher.register(this.handleActions); } render() { diff --git a/ui/src/components/UserForm.js b/ui/src/components/UserForm.js index 9decbea80..acb0885f2 100644 --- a/ui/src/components/UserForm.js +++ b/ui/src/components/UserForm.js @@ -59,7 +59,7 @@ class UserForm extends Component { Is active  

diff --git a/ui/src/index.js b/ui/src/index.js index a6f2e923e..d017df080 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -10,6 +10,7 @@ import CreateApplication from "./views/applications/CreateApplication"; import UpdateApplication from "./views/applications/UpdateApplication"; import ApplicationUsers from "./views/applications/ApplicationUsers"; import CreateApplicationUser from "./views/applications/CreateApplicationUser"; +import UpdateApplicationUser from "./views/applications/UpdateApplicationUser"; // nodes import ListNodes from './views/nodes/ListNodes'; @@ -48,6 +49,7 @@ ReactDOM.render( + diff --git a/ui/src/stores/ApplicationStore.js b/ui/src/stores/ApplicationStore.js index 2d6802741..6a2696431 100644 --- a/ui/src/stores/ApplicationStore.js +++ b/ui/src/stores/ApplicationStore.js @@ -83,6 +83,16 @@ class ApplicationStore extends EventEmitter { .catch(errorHandler); } + getUser(applicationID, userID, callbackFunc) { + fetch("/api/applications/"+applicationID+"/users/"+userID, {headers: sessionStore.getHeader()}) + .then(checkStatus) + .then((response) => response.json()) + .then((responseData) => { + callbackFunc(responseData); + }) + .catch(errorHandler); + } + removeUser(applicationID, userID, callbackFunc) { fetch("/api/applications/"+applicationID+"/users/"+userID, {method: "DELETE", headers: sessionStore.getHeader()}) .then(checkStatus) diff --git a/ui/src/stores/SessionStore.js b/ui/src/stores/SessionStore.js index f819451b8..56d8b4112 100644 --- a/ui/src/stores/SessionStore.js +++ b/ui/src/stores/SessionStore.js @@ -1,8 +1,8 @@ import { EventEmitter } from "events"; -import { checkStatus } from "./helpers"; +import { errorHandler, checkStatus } from "./helpers"; import dispatcher from "../dispatcher"; -var errorHandler = (error) => { +var loginErrorHandler = (error) => { error.then((data) => { dispatcher.dispatch({ type: "CREATE_ERROR", @@ -48,7 +48,7 @@ class SessionStore extends EventEmitter { this.setToken(responseData.jwt); this.fetchProfile(callbackFunc); }) - .catch(errorHandler); + .catch(loginErrorHandler); } fetchProfile(callbackFunc) { diff --git a/ui/src/views/applications/ApplicationUsers.js b/ui/src/views/applications/ApplicationUsers.js index 578bcd401..40e0e6af3 100644 --- a/ui/src/views/applications/ApplicationUsers.js +++ b/ui/src/views/applications/ApplicationUsers.js @@ -5,36 +5,15 @@ import ApplicationStore from "../../stores/ApplicationStore"; import Pagination from "../../components/Pagination"; class ApplicationUserRow extends Component { - constructor() { - super(); - - this.onDelete = this.onDelete.bind(this); - this.toggleAdmin = this.toggleAdmin.bind(this); - } - - onDelete() { - if (confirm("Are you sure you want to delete this application user (this does not remove the user itself)?")) { - ApplicationStore.removeUser(this.props.application.id, this.props.user.id, (responseData) => {}); - } - } - - toggleAdmin(e) { - e.preventDefault(); - ApplicationStore.updateUser(this.props.application.id, this.props.user.id, {isAdmin: !this.props.user.isAdmin}, (responseData) => {}); - } - render() { return( {this.props.user.id} - {this.props.user.username} - - - + {this.props.user.username} - + ); @@ -58,23 +37,17 @@ class ApplicationUsers extends Component { } componentDidMount() { - this.updatePage(this.props); - } - - componentWillReceiveProps(nextProps) { - this.updatePage(nextProps); - } - - componentWillMount() { ApplicationStore.getApplication(this.props.params.applicationID, (application) => { this.setState({ application: application, }); }); - ApplicationStore.on("change", () => { - this.updatePage(this.props); - }); + this.updatePage(this.props); + } + + componentWillReceiveProps(nextProps) { + this.updatePage(nextProps); } updatePage(props) { @@ -114,7 +87,6 @@ class ApplicationUsers extends Component { ID Username Admin - diff --git a/ui/src/views/applications/CreateApplicationUser.js b/ui/src/views/applications/CreateApplicationUser.js index e01485fef..9bd6ccb40 100644 --- a/ui/src/views/applications/CreateApplicationUser.js +++ b/ui/src/views/applications/CreateApplicationUser.js @@ -12,6 +12,7 @@ class AssignUserForm extends Component { this.state = { user: {}, + initialOptions: [], }; this.handleSubmit = this.handleSubmit.bind(this); @@ -19,6 +20,21 @@ class AssignUserForm extends Component { this.onAutocomplete = this.onAutocomplete.bind(this); } + componentWillMount() { + UserStore.getAll("", 10, 0, (totalCount, users) => { + const options = users.map((user, i) => { + return { + value: user.id, + label: user.username, + }; + }); + + this.setState({ + initialOptions: options, + }); + }); + } + handleSubmit(e) { e.preventDefault(); this.props.onSubmit(this.state.user); @@ -60,13 +76,13 @@ class AssignUserForm extends Component {
- +
- +

@@ -118,10 +134,10 @@ class CreateUserForm extends Component {

- +

diff --git a/ui/src/views/applications/UpdateApplicationUser.js b/ui/src/views/applications/UpdateApplicationUser.js new file mode 100644 index 000000000..c243b4d8a --- /dev/null +++ b/ui/src/views/applications/UpdateApplicationUser.js @@ -0,0 +1,137 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router'; + +import ApplicationStore from "../../stores/ApplicationStore"; + +class UpdateApplicationUserForm extends Component { + constructor() { + super(); + + this.state = { + user: {}, + }; + + this.handleSubmit = this.handleSubmit.bind(this); + } + + componentWillReceiveProps(nextProps) { + this.setState({ + user: nextProps.user, + }); + } + + handleSubmit(e) { + e.preventDefault(); + this.props.onSubmit(this.state.user); + } + + onChange(field, e) { + let user = this.state.user; + if (e.target.type === "checkbox") { + user[field] = e.target.checked; + } else { + user[field] = e.target.value; + } + + this.setState({ + user: user, + }); + } + + render() { + return( + +

+ + +
+
+ +
+ +
+

+ When checked, the user will be assigned admin permissions within the context of the application. +

+
+
+ + + ); + } +} + +class UpdateApplicationUser extends Component { + static contextTypes = { + router: React.PropTypes.object.isRequired + }; + + constructor() { + super(); + + this.state = { + application: {}, + user: {}, + }; + + this.onSubmit = this.onSubmit.bind(this); + this.onDelete = this.onDelete.bind(this); + } + + componentWillMount() { + ApplicationStore.getApplication(this.props.params.applicationID, (application) => { + this.setState({ + application: application, + }); + }); + + ApplicationStore.getUser(this.props.params.applicationID, this.props.params.userID, (user) => { + this.setState({ + user: user, + }); + }); + } + + onSubmit(user) { + ApplicationStore.updateUser(this.props.params.applicationID, this.props.params.userID, user, (responseData) => { + this.context.router.push("/applications/"+this.props.params.applicationID+"/users"); + }); + } + + onDelete() { + if (confirm("Are you sure you want to delete this application user (this does not remove the user itself)?")) { + ApplicationStore.removeUser(this.props.params.applicationID, this.props.params.userID, (responseData) => { + this.context.router.push("/applications/"+this.props.params.applicationID+"/users"); + }); + } + } + + render() { + return( +
+
    +
  1. Dashboard
  2. +
  3. Applications
  4. +
  5. {this.state.application.name}
  6. +
  7. Users
  8. +
  9. {this.state.user.username}
  10. +
+
+
+ +
+
+
+
+
+ +
+
+
+ ); + } +} + +export default UpdateApplicationUser; diff --git a/ui/src/views/users/CreateUser.js b/ui/src/views/users/CreateUser.js index a15a57029..08ac82ee2 100644 --- a/ui/src/views/users/CreateUser.js +++ b/ui/src/views/users/CreateUser.js @@ -13,7 +13,9 @@ class CreateUser extends Component { super(); this.state = { - user: {}, + user: { + isActive: true, + }, }; this.onSubmit = this.onSubmit.bind(this);