Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi tenant support in webconsole #18

Merged
merged 16 commits into from
Jan 20, 2022
Merged
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