Skip to content

Commit

Permalink
Multi tenant support in webconsole (#18)
Browse files Browse the repository at this point in the history
* Initial commit for multi-tenant handling.

* Update muiti tenant code.

* Add Tenant UI.

* Tenant/User API routing.

* Update Tenant APIs.

* Update User APIs.

* Tenant related files.

* Update tenant code.

* Update tenant UI.

* Add User related UI.

* Check email duplication.

* Update check for email and token.

* Remove tenant and change email address fix.

* Prevent to add same UI ID subscription of other tenant.

* Update to sync to the latest code.
  • Loading branch information
kishiguro authored Jan 20, 2022
1 parent b652f99 commit aa182fa
Show file tree
Hide file tree
Showing 25 changed files with 1,501 additions and 13 deletions.
516 changes: 510 additions & 6 deletions backend/WebUI/api_webui.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions backend/WebUI/model_tenant_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package WebUI

type Tenant struct {
TenantId string `json:"tenantId"`
TenantName string `json:"tenantName"`
}
8 changes: 8 additions & 0 deletions backend/WebUI/model_user_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package WebUI

type User struct {
UserId string `json:"userId"`
TenantId string `json:"tenantId"`
Email string `json:"email"`
EncryptedPassword string `json:"encryptedPassword"`
}
84 changes: 84 additions & 0 deletions backend/WebUI/routers.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,90 @@ var routes = Routes{
GetSampleJSON,
},

{
"Login",
http.MethodPost,
"/login",
Login,
},

{
"Logout",
http.MethodPost,
"/logout",
Logout,
},

{
"GetTenants",
http.MethodGet,
"/tenant",
GetTenants,
},

{
"GetTenantByID",
http.MethodGet,
"/tenant/:tenantId",
GetTenantByID,
},

{
"PostTenant",
http.MethodPost,
"/tenant",
PostTenant,
},

{
"PutTenantByID",
http.MethodPut,
"/tenant/:tenantId",
PutTenantByID,
},

{
"DeleteTenantByID",
http.MethodDelete,
"/tenant/:tenantId",
DeleteTenantByID,
},

{
"GetUsers",
http.MethodGet,
"/tenant/:tenantId/user",
GetUsers,
},

{
"GetUserByID",
http.MethodGet,
"/tenant/:tenantId/user/:userId",
GetUserByID,
},

{
"PostUserByID",
http.MethodPost,
"/tenant/:tenantId/user",
PostUserByID,
},

{
"PutUserByID",
http.MethodPut,
"/tenant/:tenantId/user/:userId",
PutUserByID,
},

{
"DeleteUserByID",
http.MethodDelete,
"/tenant/:tenantId/user/:userId",
DeleteUserByID,
},

{
"GetSubscribers",
http.MethodGet,
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/SideBar/Nav.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import React, {Component} from 'react';
import {Link, withRouter} from 'react-router-dom';
import LocalStorageHelper from "../../util/LocalStorageHelper";

class Nav extends Component {
state = {};

render() {
let {location} = this.props;
let user = LocalStorageHelper.getUserInfo();
let childView = "";
if (user.accessToken === "admin") {
childView = (
<li className={this.isPathActive('/tenants') ? 'active' : null}>
<Link to="/tenants">
<i className="pe-7s-users"/>
<p>Tenant and User</p>
</Link>
</li>
);
}

/* Icons:
* - https://fontawesome.com/icons
* - http://themes-pixeden.com/font-demos/7-stroke/
Expand Down Expand Up @@ -33,6 +47,8 @@ class Nav extends Component {
</Link>
</li>

{childView}

</ul>
);
}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/models/Tenant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Serializable from "./Serializable";

export default class Tenant extends Serializable{
id = '';
name = "";

constructor(id, name) {
super();
this.id = id;
this.name = name;
}
}
8 changes: 7 additions & 1 deletion frontend/src/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ export default class User extends Serializable{
username = "";
name = "";
imageUrl = "";
accessToken = "";
id = '';
email = "";

constructor(username, name) {
constructor(username, name, accessToken, id, email) {
super();
this.username = username;
this.name = name;
this.imageUrl = 'https://cdn1.iconfinder.com/data/icons/evil-icons-user-interface/64/avatar-256.png';
this.accessToken = accessToken;
this.id = id;
this.email = email;
}
}
6 changes: 5 additions & 1 deletion frontend/src/pages/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import SideBar from '../../components/SideBar';
import Subscribers from '../Subscribers';
import Tasks from '../Tasks';
import UEInfo from '../Dashboard';
import UEInfoDetail from '../UEInfoDetail'
import UEInfoDetail from '../UEInfoDetail';
import Tenants from '../Tenants';
import Users from '../Users';

const Main = ({
mobileNavVisibility,
Expand Down Expand Up @@ -47,6 +49,8 @@ const Main = ({
<Route exact path="/tasks" component={Tasks}/>
<Route exact path="/ueinfo" component={UEInfo}/>
<Route exact path="/ueinfo/:id" component={UEInfoDetail}/>
<Route exact path="/tenants" component={Tenants}/>
<Route exact path="/users/:id" component={Users}/>

<Footer/>
</div>
Expand Down
132 changes: 132 additions & 0 deletions frontend/src/pages/Tenants/TenantOverview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { Component } from 'react';
import { Link, withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { Button, Table } from "react-bootstrap";
import TenantModal from "./components/TenantModal";
import ApiHelper from "../../util/ApiHelper";

class TenantOverview extends Component {
state = {
tenantModalOpen: false,
tenantModalData: null,
};

componentDidMount() {
ApiHelper.fetchTenants().then();
}

openAddTenant() {
this.setState({
tenantModalOpen: true,
tenantModalData: null,
});
}

/**
* @param tenantId {string}
*/
async openEditTenant(tenantId) {
const tenant = await ApiHelper.fetchTenantById(tenantId);

this.setState({
tenantModalOpen: true,
tenantModalData: tenant,
});
}

async addTenant(tenantData) {
this.setState({ tenantModalOpen: false });

if (!await ApiHelper.createTenant(tenantData)) {
alert("Error creating new tenant");
}
ApiHelper.fetchTenants().then();
}

/**
* @param tenantData
*/
async updateTenant(tenantData) {
this.setState({ tenantModalOpen: false });

const result = await ApiHelper.updateTenant(tenantData);

if (!result) {
alert("Error updating tenant: " + tenantData["ueId"]);
}
ApiHelper.fetchTenants().then();
}

/**
* @param tenant {Tenant}
*/
async deleteTenant(tenant) {
if (!window.confirm(`Delete tenant ${tenant.id}?`))
return;

const result = await ApiHelper.deleteTenant(tenant.id);
ApiHelper.fetchTenants().then();
if (!result) {
alert("Error deleting tenant: " + tenant.id);
}
}

render() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="card">
<div className="header subscribers__header">
<h4>Tenants</h4>
<Button bsStyle={"primary"} className="subscribers__button"
onClick={this.openAddTenant.bind(this)}>
New Tenant
</Button>
</div>
<div className="content subscribers__content">
<Table className="subscribers__table" striped bordered condensed hover>
<thead>
<tr>
<th style={{ width: 400 }}>Tenant ID</th>
<th colSpan={2}>Tenant Name</th>
</tr>
</thead>
<tbody>
{this.props.tenants.map(tenant => (
<tr key={tenant.id}>
<td>{tenant.id}</td>
<td><font color="blue"><u><Link to={"/users/"+tenant.id}>{tenant.name}</Link></u></font></td>
<td style={{ textAlign: 'center' }}>
<Button variant="danger" onClick={this.deleteTenant.bind(this, tenant)}>Delete</Button>
&nbsp;&nbsp;&nbsp;&nbsp;
<Button variant="info" onClick={this.openEditTenant.bind(this, tenant.id)}>Modify</Button>
</td>
</tr>
))}
</tbody>
</Table>

<p>&nbsp;</p><p>&nbsp;</p>
<p>&nbsp;</p><p>&nbsp;</p>
<p>&nbsp;</p><p>&nbsp;</p>
</div>
</div>
</div>
</div>

<TenantModal open={this.state.tenantModalOpen}
setOpen={val => this.setState({ tenantModalOpen: val })}
tenant={this.state.tenantModalData}
onModify={this.updateTenant.bind(this)}
onSubmit={this.addTenant.bind(this)} />
</div>
);
}
}

const mapStateToProps = state => ({
tenants: state.tenant.tenants,
});

export default withRouter(connect(mapStateToProps)(TenantOverview));
Loading

0 comments on commit aa182fa

Please sign in to comment.