Skip to content

Commit

Permalink
Various ui improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
brocaar committed Mar 16, 2017
1 parent 5818ee7 commit 2730dcd
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 46 deletions.
2 changes: 2 additions & 0 deletions internal/storage/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
9 changes: 8 additions & 1 deletion ui/src/Layout.js
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<Navbar />
<div className="container">
<div className="container" onClick={this.onClick}>
<div className="row">
<Errors />
{this.props.children}
Expand Down
17 changes: 17 additions & 0 deletions ui/src/components/Navbar.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -17,6 +18,7 @@ class Navbar extends Component {
}

this.toggleDropdown = this.toggleDropdown.bind(this);
this.handleActions = this.handleActions.bind(this);
}

toggleDropdown() {
Expand All @@ -25,13 +27,28 @@ 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({
user: SessionStore.getUser(),
isAdmin: SessionStore.isAdmin(),
});
});

dispatcher.register(this.handleActions);
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/UserForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class UserForm extends Component {
<input type="checkbox" name="isActive" id="isActive" checked={this.state.user.isActive} onChange={this.onChange.bind(this, 'isActive')} /> Is active &nbsp;
</label>
<label className="checkbox-inline">
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is admin &nbsp;
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is global admin &nbsp;
</label>
</div>
<hr />
Expand Down
2 changes: 2 additions & 0 deletions ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -48,6 +49,7 @@ ReactDOM.render(
<Route path="applications/:applicationID/nodes/:devEUI/activation" component={ActivateNode}></Route>
<Route path="applications/:applicationID/users" component={ApplicationUsers}></Route>
<Route path="applications/:applicationID/users/create" component={CreateApplicationUser}></Route>
<Route path="applications/:applicationID/users/:userID/edit" component={UpdateApplicationUser}></Route>
<Route path="applications/:applicationID" component={ListNodes}></Route>
<Route path="channels" component={ChannelLists}></Route>
<Route path="channels/create" component={CreateChannelList}></Route>
Expand Down
10 changes: 10 additions & 0 deletions ui/src/stores/ApplicationStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions ui/src/stores/SessionStore.js
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -48,7 +48,7 @@ class SessionStore extends EventEmitter {
this.setToken(responseData.jwt);
this.fetchProfile(callbackFunc);
})
.catch(errorHandler);
.catch(loginErrorHandler);
}

fetchProfile(callbackFunc) {
Expand Down
42 changes: 7 additions & 35 deletions ui/src/views/applications/ApplicationUsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<tr>
<td>{this.props.user.id}</td>
<td>{this.props.user.username}</td>
<td>
<a href="#" onClick={this.toggleAdmin}>
<span className={"glyphicon glyphicon-" + (this.props.user.isAdmin ? 'ok' : 'remove')} aria-hidden="true"></span>
</a>
<Link to={`applications/${this.props.application.id}/users/${this.props.user.id}/edit`}>{this.props.user.username}</Link>
</td>
<td>
<button type="button" className="btn btn-link btn-xs" onClick={this.onDelete}>Remove</button>
<span className={"glyphicon glyphicon-" + (this.props.user.isAdmin ? 'ok' : 'remove')} aria-hidden="true"></span>
</td>
</tr>
);
Expand All @@ -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) {
Expand Down Expand Up @@ -114,7 +87,6 @@ class ApplicationUsers extends Component {
<th className="col-md-1">ID</th>
<th>Username</th>
<th className="col-md-1">Admin</th>
<th className="col-md-1"></th>
</tr>
</thead>
<tbody>
Expand Down
26 changes: 21 additions & 5 deletions ui/src/views/applications/CreateApplicationUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,29 @@ class AssignUserForm extends Component {

this.state = {
user: {},
initialOptions: [],
};

this.handleSubmit = this.handleSubmit.bind(this);
this.onAutocompleteSelect = this.onAutocompleteSelect.bind(this);
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);
Expand Down Expand Up @@ -60,13 +76,13 @@ class AssignUserForm extends Component {
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label className="control-label" htmlFor="name">Username</label>
<Select.Async name="username" loadOptions={this.onAutocomplete} value={this.state.user.userID} onChange={this.onAutocompleteSelect} clearable={false} autoload={false} />
<Select.Async name="username" required options={this.state.initialOptions} loadOptions={this.onAutocomplete} value={this.state.user.userID} onChange={this.onAutocompleteSelect} clearable={false} autoload={false} />
</div>
<div className="form-group">
<label className="control-label">Is admin</label>
<label className="control-label">Admin</label>
<div className="checkbox">
<label>
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is admin
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is application admin
</label>
</div>
<p className="help-block">
Expand Down Expand Up @@ -118,10 +134,10 @@ class CreateUserForm extends Component {
<input className="form-control" id="password" type="password" placeholder="password" value={this.state.user.password || ''} onChange={this.onChange.bind(this, 'password')} />
</div>
<div className="form-group">
<label className="control-label">Is admin</label>
<label className="control-label">Admin</label>
<div className="checkbox">
<label>
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is admin
<input type="checkbox" name="isAdmin" id="isAdmin" checked={this.state.user.isAdmin} onChange={this.onChange.bind(this, 'isAdmin')} /> Is application admin
</label>
</div>
<p className="help-block">
Expand Down
Loading

0 comments on commit 2730dcd

Please sign in to comment.