(web) Hooked up node screen with the API

This commit is contained in:
Alexey Kontsevoy 2016-02-25 18:55:44 -05:00
parent 16b0f15dc5
commit 59d5f43182
28 changed files with 611 additions and 328 deletions

299
web/dist/app/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

294
web/dist/app/vendor.js vendored

File diff suppressed because one or more lines are too long

8
web/dist/index.html vendored
View file

@ -10,11 +10,11 @@
<script src="/web/app/assets/js/jquery-validate-1.14.0.js"></script>
<script src="/web/app/assets/js/bootstrap.js"></script>
<script src="/web/app/assets/js/term.js"></script>
<script src="/web/app/vendor.js?ver=0.11456421481641"></script>
<script src="/web/app/styles.js?ver=0.11456421481641"></script>
<script src="/web/app/vendor.js?ver=0.11456438265918"></script>
<script src="/web/app/styles.js?ver=0.11456438265918"></script>
</head>
<body>
<body class="grv">
<div id="app"></div>
</body>
<script src="/web/app/app.js?ver=0.11456421481641"></script>
<script src="/web/app/app.js?ver=0.11456438265918"></script>
</html>

View file

@ -6,5 +6,6 @@ module.exports.$ = require('jQuery');
module.exports.Dfd = module.exports.$.Deferred;
module.exports.spyOn = module.exports.expect.spyOn;
module.exports.reactor = require('app/reactor');
module.exports.session = require('app/session');
module.exports.api = require('app/services/api');
require('app/modules');

View file

@ -12,12 +12,12 @@ describe('modules/nodes', function () {
describe('getters and actions', function () {
beforeEach(function () {
api.get.andReturn(Dfd().resolve([{id: 1, ip: '127.3.4.5:1000', count: 1, name: 'alex'}, {id: 2, ip: '227.1.1.5:1000', count: 4, name: 'martha'}]));
api.get.andReturn(Dfd().resolve({ "nodes": [ { "addr": "127.0.0.1:8080", "hostname": "a.example.com", "labels": {"role": "mysql"}, "cmd_labels": { "db_status": { "command": "mysql -c status", "result": "master", "period": 1000000000 }} } ] }) );
actions.fetchNodes();
});
it('should handle "nodeInfos"', function () {
var expected = [{"count":1,"ip":"127.3.4.5:1000","tags":["tag1","tag2","tag3"],"roles":["r1","r2","r3"]},{"count":4,"ip":"227.1.1.5:1000","tags":["tag1","tag2","tag3"],"roles":["r1","r2","r3"]}];
var expected = [{"tags":[{"role":"role","value":"mysql"},{"role":"db_status","value":"master","tooltip":"mysql -c status"}],"ip":"127.0.0.1:8080"}]
expect(reactor.evaluateToJS(getters.nodeListView)).toEqual(expected);
});

View file

@ -0,0 +1,23 @@
var { reactor, expect, Dfd, spyOn } = require('./../');
var {actions, getters} = require('app/modules/user');
var {TLPT_RECEIVE_USER} = require('app/modules/user/actionTypes');
describe('modules/nodes', function () {
afterEach(function () {
reactor.reset()
})
describe('getters', function () {
beforeEach(function () {
var sample = {"type": "bearer", "token": "bearer token", "user": {"name": "alex", "allowed_logins": ["admin", "bob"]}, "expires_in": 20};
reactor.dispatch(TLPT_RECEIVE_USER, sample.user);
});
it('should return "user"', function () {
var expected = {"name":"alex","logins":["admin","bob"]};
expect(reactor.evaluateToJS(getters.user)).toEqual(expected);
});
});
})

View file

@ -25,13 +25,14 @@ var auth = {
},
ensureUser(){
if(session.getUserData()){
var userData = session.getUserData();
if(userData){
// refresh timer will not be set in case of browser refresh event
if(auth._getRefreshTokenTimerId() === null){
return auth._login().done(auth._startTokenRefresher);
}
return $.Deferred().resolve();
return $.Deferred().resolve(userData);
}
return $.Deferred().reject();

View file

@ -5,9 +5,9 @@ var cfg = require('app/config');
var App = React.createClass({
render: function() {
return (
<div className="grv">
<div className="grv-tlpt">
<NavLeftBar/>
<div className="row" style={{marginLeft: "70px"}}>
<div className="row">
<nav className="" role="navigation" style={{ marginBottom: 0 }}>
<ul className="nav navbar-top-links navbar-right">
<li>
@ -24,7 +24,7 @@ var App = React.createClass({
</ul>
</nav>
</div>
<div style={{ 'marginLeft': '100px' }}>
<div className="grv-page">
{this.props.children}
</div>
</div>

View file

@ -64,7 +64,7 @@ var Login = React.createClass({
var isError = false;//this.state.userRequest.get('isError');
return (
<div className="grv grv-login text-center">
<div className="grv-login text-center">
<div className="grv-logo-tprt"></div>
<div className="grv-content grv-flex">
<div className="grv-flex-column">

View file

@ -121,7 +121,7 @@ var Invite = React.createClass({
}
return (
<div className="grv grv-invite text-center">
<div className="grv-invite text-center">
<div className="grv-logo-tprt"></div>
<div className="grv-content grv-flex">
<div className="grv-flex-column">

View file

@ -1,6 +1,7 @@
var React = require('react');
var reactor = require('app/reactor');
var {getters, actions} = require('app/modules/nodes');
var userGetters = require('app/modules/user/getters');
var {Table, Column, Cell} = require('app/components/table.jsx');
const TextCell = ({rowIndex, data, columnKey, ...props}) => (
@ -9,13 +10,58 @@ const TextCell = ({rowIndex, data, columnKey, ...props}) => (
</Cell>
);
const TagCell = ({rowIndex, data, columnKey, ...props}) => (
<Cell {...props}>
{ data[rowIndex].tags.map((item, index) =>
(<span key={index} className="label label-default">
{item.role} <li className="fa fa-long-arrow-right"></li>
{item.value}
</span>)
) }
</Cell>
);
const LoginCell = ({user, ...props}) => {
if(!user || user.logins.length === 0){
return <Cell {...props} />;
}
var $lis = [];
for(var i = 0; i < user.logins.length; i++){
$lis.push(<li key={i}><a href="#" target="_blank">{user.logins[i]}</a></li>);
}
return (
<Cell {...props}>
<div className="btn-group">
<button type="button" className="btn btn-sm btn-primary">{user.logins[0]}</button>
{
$lis.length > 1 ? (
<div className="btn-group">
<button data-toggle="dropdown" className="btn btn-default btn-sm dropdown-toggle" aria-expanded="true">
<span className="caret"></span>
</button>
<ul className="dropdown-menu">
<li><a href="#" target="_blank">Logs</a></li>
<li><a href="#" target="_blank">Logs</a></li>
</ul>
</div>
): null
}
</div>
</Cell>
)
};
var Nodes = React.createClass({
mixins: [reactor.ReactMixin],
getDataBindings() {
return {
nodeRecords: getters.nodeListView
nodeRecords: getters.nodeListView,
user: userGetters.user
}
},
@ -29,12 +75,12 @@ var Nodes = React.createClass({
render: function() {
var data = this.state.nodeRecords;
return (
<div>
<div className="grv-nodes">
<h1> Nodes </h1>
<div className="">
<div className="">
<div className="">
<Table rowCount={data.length}>
<Table rowCount={data.length} className="grv-nodes-table">
<Column
columnKey="count"
header={<Cell> Sessions </Cell> }
@ -48,12 +94,12 @@ var Nodes = React.createClass({
<Column
columnKey="tags"
header={<Cell></Cell> }
cell={<TextCell data={data}/> }
cell={<TagCell data={data}/> }
/>
<Column
columnKey="roles"
header={<Cell>Login as</Cell> }
cell={<TextCell data={data}/> }
cell={<LoginCell user={this.state.user}/> }
/>
</Table>
</div>

View file

@ -22,7 +22,7 @@ var GrvTable = React.createClass({
var rows = [];
for(var i = 0; i < count; i ++){
var cells = children.map((item, index)=>{
return this.renderCell(item.props.cell, {rowIndex: i, key: i, isHeader: false, ...item.props});
return this.renderCell(item.props.cell, {rowIndex: i, key: index, isHeader: false, ...item.props});
})
rows.push(<tr key={i}>{cells}</tr>);
@ -56,8 +56,10 @@ var GrvTable = React.createClass({
children.push(child);
});
var tableClass = 'table table-bordered ' + this.props.className;
return (
<table className="table table-bordered">
<table className={tableClass}>
{this.renderHeader(children)}
{this.renderBody(children)}
</table>

View file

@ -5,7 +5,7 @@ let cfg = {
baseUrl: window.location.origin,
api: {
nodesPath: '/nodes',
nodesPath: '/v1/webapi/sites/-current-/nodes',
sessionPath: '/v1/webapi/sessions',
invitePath: '/v1/webapi/users/invites/:inviteToken',
createUserPath: '/v1/webapi/users',

View file

@ -2,6 +2,7 @@ var React = require('react');
var render = require('react-dom').render;
var { Router, Route, Redirect, IndexRoute, browserHistory } = require('react-router');
var { App, Login, Nodes, Sessions, NewUser } = require('./components');
var {ensureUser} = require('./modules/user/actions');
var auth = require('./auth');
var session = require('./session');
var cfg = require('./config');
@ -11,15 +12,6 @@ require('./modules');
// init session
session.init();
function requiresAuth(nextState, replace, cb) {
auth.ensureUser()
.done(()=> cb())
.fail(()=>{
replace({redirectTo: nextState.location.pathname }, cfg.routes.login);
cb();
});
}
function handleLogout(nextState, replace, cb){
auth.logout();
// going back will hit requireAuth handler which will redirect it to the login page
@ -31,8 +23,8 @@ render((
<Route path={cfg.routes.login} component={Login}/>
<Route path={cfg.routes.logout} onEnter={handleLogout}/>
<Route path={cfg.routes.newUser} component={NewUser}/>
<Route path={cfg.routes.app} component={App} onEnter={requiresAuth}>
<Route path={cfg.routes.app} component={App} onEnter={ensureUser} >
<IndexRoute component={Nodes}/>
<Route path={cfg.routes.nodes} component={Nodes}/>
<Route path={cfg.routes.sessions} component={Sessions}/>
</Route>

View file

@ -5,8 +5,8 @@ var cfg = require('app/config');
export default {
fetchNodes(){
api.get(cfg.api.nodes).done(nodes=>{
reactor.dispatch(TLPT_RECEIVE_NODES, nodes);
api.get(cfg.api.nodesPath).done(data=>{
reactor.dispatch(TLPT_RECEIVE_NODES, data.nodes);
});
}
}

View file

@ -1,17 +1,44 @@
//var sort = require('app/common/sort');
const nodeListView = [ ['tlpt_nodes'], (nodes) =>{
return nodes.valueSeq().map((item)=>{
return nodes.map((item)=>{
return {
count: item.get('count'),
ip: item.get('ip'),
tags: ['tag1', 'tag2', 'tag3'],
roles: ['r1', 'r2', 'r3']
tags: getTags(item),
ip: item.get('addr')
}
}).toJS();
}
];
function getTags(node){
var allLabels = [];
var labels = node.get('labels');
if(labels){
labels.entrySeq().toArray().forEach(item=>{
allLabels.push({
role: item[0],
value: item[1]
});
});
}
labels = node.get('cmd_labels');
if(labels){
labels.entrySeq().toArray().forEach(item=>{
allLabels.push({
role: item[0],
value: item[1].get('result'),
tooltip: item[1].get('command')
});
});
}
return allLabels;
}
export default {
nodeListView
}

View file

@ -3,7 +3,7 @@ var { TLPT_RECEIVE_NODES } = require('./actionTypes');
export default Store({
getInitialState() {
return toImmutable({});
return toImmutable([]);
},
initialize() {
@ -11,10 +11,6 @@ export default Store({
}
})
function receiveNodes(state, nodeArrayData){
return state.withMutations(state => {
nodeArrayData.forEach((item) => {
state.set(item.id, toImmutable(item))
})
});
function receiveNodes(state, nodeArray){
return toImmutable(nodeArray);
}

View file

@ -8,11 +8,27 @@ var cfg = require('app/config');
export default {
ensureUser(nextState, replace, cb){
/*var userData = session.getUserData();
reactor.dispatch(TLPT_RECEIVE_USER, userData.user);
cb();*/
auth.ensureUser()
.done((userData)=> {
reactor.dispatch(TLPT_RECEIVE_USER, userData.user);
cb();
})
.fail(()=>{
replace({redirectTo: nextState.location.pathname }, cfg.routes.login);
cb();
});
},
signUp({name, psw, token, inviteToken}){
restApiActions.start(TRYING_TO_SIGN_UP);
auth.signUp(name, psw, token, inviteToken)
.done((user)=>{
reactor.dispatch(TLPT_RECEIVE_USER, user);
.done((sessionData)=>{
reactor.dispatch(TLPT_RECEIVE_USER, sessionData.user);
restApiActions.success(TRYING_TO_SIGN_UP);
session.getHistory().push({pathname: cfg.routes.app});
})
@ -23,8 +39,8 @@ export default {
login({user, password, token}, redirect){
auth.login(user, password, token)
.done((user)=>{
reactor.dispatch(TLPT_RECEIVE_USER, user);
.done((sessionData)=>{
reactor.dispatch(TLPT_RECEIVE_USER, sessionData.user);
session.getHistory().push({pathname: redirect});
})
.fail(()=>{

View file

@ -0,0 +1,15 @@
const user = [ ['tlpt_user'], (currentUser) => {
if(!currentUser){
return null;
}
return {
name: currentUser.get('name'),
logins: currentUser.get('allowed_logins').toJS()
}
}
];
export default {
user
}

View file

@ -1,16 +1,3 @@
module.exports.getters = require('./getters');
module.exports.actions = require('./actions');
module.exports.nodeStore = require('./userStore');
// nodes: [{"id":"x220","addr":"0.0.0.0:3022","hostname":"x220","labels":null,"cmd_labels":null}]
// sessions: [{"id":"07630636-bb3d-40e1-b086-60b2cae21ac4","parties":[{"id":"89f762a3-7429-4c7a-a913-766493fe7c8a","site":"127.0.0.1:37514","user":"akontsevoy","server_addr":"0.0.0.0:3022","last_active":"2016-02-22T14:39:20.93120535-05:00"}]}]
/*
let TodoRecord = Immutable.Record({
id: 0,
description: "",
completed: false
});
*/

View file

@ -13,7 +13,7 @@
<script src="/web/app/vendor.js?ver=[VERSION]"></script>
<script src="/web/app/styles.js?ver=[VERSION]"></script>
</head>
<body>
<body class="grv">
<div id="app"></div>
</body>
<script src="/web/app/app.js?ver=[VERSION]"></script>

View file

@ -28,15 +28,15 @@ $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
// Components
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/badges";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/jumbotron";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/thumbnails";

View file

@ -1,41 +1,34 @@
@import "grv-invite";
@import "grv-login";
@import "grv-logo";
@import "grv-nodes";
.grv-logo-tprt{
color: white;
margin-top: 5%;
margin: 0 auto;
width: 67px;
height: 70px;
background-size: contain;
background-repeat: no-repeat;
background-image: url("/web/app/assets/img/grv-tlpt-logo.png");
}
.grv-google-auth{
text-align: initial;
position: relative;
margin: 15px 0 0 0;
padding: 0 0 0 70px;
.grv-google-auth-icon{
position: absolute;
left: 15px;
height: 40px;
width: 40px;
background-size: contain;
background-repeat: no-repeat;
background-image: url("/web/app/assets/img/authenticator-icon.png");
}
}
.grv {
min-width: 1024px;
background-color: white;
#app{
height: 100%;
}
.grv-spinner.sk-spinner-three-bounce{
position: absolute;
top: 50%;
left: 50%;
}
nav{
height: 100%;
background-color: #293846;
font-size: 20px;
}
.grv-page{
margin-left: 60px;
padding: 0 15px;
}
}
.grv-flex{

View file

@ -1,6 +1,11 @@
.grv-invite{
margin-top: 5%;
background-color: #2f4050;
padding-top: 5%;
height: 100%;
width: 100%;
position: fixed;
overflow: auto;
.grv-content{
padding: 15px;

View file

@ -1,6 +1,10 @@
.grv-login{
margin-top: 5%;
background-color: #2f4050;
padding-top: 5%;
height: 100%;
width: 100%;
position: fixed;
overflow: auto;
.grv-content{
padding: 15px;

View file

@ -0,0 +1,27 @@
.grv-logo-tprt{
color: white;
margin-top: 5%;
margin: 0 auto;
width: 67px;
height: 70px;
background-size: contain;
background-repeat: no-repeat;
background-image: url("/web/app/assets/img/grv-tlpt-logo.png");
}
.grv-google-auth{
text-align: initial;
position: relative;
margin: 15px 0 0 0;
padding: 0 0 0 70px;
.grv-google-auth-icon{
position: absolute;
left: 15px;
height: 40px;
width: 40px;
background-size: contain;
background-repeat: no-repeat;
background-image: url("/web/app/assets/img/authenticator-icon.png");
}
}

View file

@ -0,0 +1,9 @@
.grv-nodes{
.grv-nodes-table{
.label {
margin-right: 5px;
}
}
}