handle 403 and display local and node ip in archived list

This commit is contained in:
Alexey Kontsevoy 2016-10-21 18:00:07 -04:00
parent a86ec3bdd8
commit 0ef1d46d58
27 changed files with 824 additions and 695 deletions

View file

@ -3,12 +3,12 @@ webpackJsonp([1],{
/***/ 0:
/***/ function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(460);
module.exports = __webpack_require__(461);
/***/ },
/***/ 460:
/***/ 461:
/***/ function(module, exports) {
// removed by extract-text-webpack-plugin

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View file

@ -6,9 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Teleport by Gravitational</title>
<script src="/web/config.js"></script>
<link rel="shortcut icon" href="/web/app/favicon.ico"><link href="/web/app/vendor.3cef2e9ca8fbd0402d43e14af05c3521.css" rel="stylesheet"></head>
<link rel="shortcut icon" href="/web/app/favicon.ico"><link href="/web/app/vendor.12cdb3da5b237d375fb2d5687e83bcdf.css" rel="stylesheet"></head>
<body class="grv">
<div id="app"></div>
<div id="bearer_token" style="display: none;">{{.Session}}</div>
<script type="text/javascript" src="/web/app/vendor.dfe7f8b338cfdef83b06.js"></script><script type="text/javascript" src="/web/app/styles.dfe7f8b338cfdef83b06.js"></script><script type="text/javascript" src="/web/app/app.dfe7f8b338cfdef83b06.js"></script></body>
<script type="text/javascript" src="/web/app/vendor.e2e926d587c1591b34f7.js"></script><script type="text/javascript" src="/web/app/styles.e2e926d587c1591b34f7.js"></script><script type="text/javascript" src="/web/app/app.e2e926d587c1591b34f7.js"></script></body>
</html>

View file

@ -18,8 +18,10 @@ var React = require('react');
var NavLeftBar = require('./navLeftBar');
var reactor = require('app/reactor');
var {actions, getters} = require('app/modules/app');
var SelectNodeDialog = require('./selectNodeDialog.jsx');
var NotificationHost = require('./notificationHost.jsx');
var Timer = require('./timer.jsx');
var App = React.createClass({
@ -35,13 +37,14 @@ var App = React.createClass({
actions.initApp();
},
render: function() {
render() {
if(this.state.app.isInitializing){
return null;
}
return (
<div className="grv-tlpt grv-flex grv-flex-row">
<Timer onTimeout={actions.checkIfUserLoggedIn}/>
<SelectNodeDialog/>
<NotificationHost/>
{this.props.CurrentSessionHost}

View file

@ -18,21 +18,12 @@ var React = require('react');
var reactor = require('app/reactor');
var userGetters = require('app/modules/user/getters');
var nodeGetters = require('app/modules/nodes/getters');
var {fetchNodes} = require('app/modules/nodes/actions');
var NodeList = require('./nodeList.jsx');
var Nodes = React.createClass({
mixins: [reactor.ReactMixin],
componentDidMount(){
this.refreshInterval = setInterval(fetchNodes, 2500);
},
componentWillUnmount: function() {
clearInterval(this.refreshInterval);
},
getDataBindings() {
return {
nodeRecords: nodeGetters.nodeListView,
@ -40,7 +31,7 @@ var Nodes = React.createClass({
}
},
render: function() {
render() {
var nodeRecords = this.state.nodeRecords;
var logins = this.state.user.logins;
return ( <NodeList nodeRecords={nodeRecords} logins={logins}/> );

View file

@ -131,10 +131,10 @@ var NodeList = React.createClass({
return (
<div className="grv-nodes grv-page">
<div className="grv-flex grv-header">
<div className="grv-flex grv-header m-t-md">
<div className="grv-flex-column"></div>
<div className="grv-flex-column">
<h1> Nodes </h1>
<h2 className="text-center no-margins"> Nodes </h2>
</div>
<div className="grv-flex-column">
<InputSearch value={this.filter} onChange={this.onFilterChange}/>

View file

@ -2,8 +2,8 @@
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
you may not use this file except in compliance with the License.
http://www.apache.org/licenses/LICENSE-2.0
@ -17,25 +17,16 @@ limitations under the License.
var React = require('react');
var {Table, Column, Cell, TextCell, EmptyIndicator} = require('app/components/table.jsx');
var {ButtonCell, UsersCell, NodeCell, DateCreatedCell} = require('./listItems');
var {fetchActiveSessions} = require('app/modules/sessions/actions');
var ActiveSessionList = React.createClass({
componentWillMount(){
fetchActiveSessions();
this.refreshInterval = setInterval(fetchActiveSessions, 2500);
},
componentWillUnmount(){
clearInterval(this.refreshInterval);
},
render: function() {
render() {
let data = this.props.data.filter(item => item.active);
return (
<div className="grv-sessions-active">
<div className="grv-header">
<h1> Active Sessions </h1>
<h2 className="text-center"> Active Sessions </h2>
</div>
<div className="grv-content">
{data.length === 0 ? <EmptyIndicator text="You have no active sessions."/> :

View file

@ -16,10 +16,13 @@ limitations under the License.
var React = require('react');
var reactor = require('app/reactor');
var {fetchActiveSessions} = require('app/modules/sessions/actions');
var { fetchStoredSession } = require('app/modules/storedSessionsFilter/actions');
var {sessionsView} = require('app/modules/sessions/getters');
var {filter} = require('app/modules/storedSessionsFilter/getters');
var StoredSessionList = require('./storedSessionList.jsx');
var ActiveSessionList = require('./activeSessionList.jsx');
var Timer = require('./../timer.jsx');
var Sessions = React.createClass({
mixins: [reactor.ReactMixin],
@ -31,10 +34,16 @@ var Sessions = React.createClass({
}
},
refresh(){
fetchStoredSession();
fetchActiveSessions();
},
render: function() {
let {data, storedSessionsFilter} = this.state;
return (
<div className="grv-sessions grv-page">
<Timer onTimeout={this.refresh} />
<ActiveSessionList data={data}/>
<hr className="grv-divider"/>
<StoredSessionList data={data} filter={storedSessionsFilter}/>

View file

@ -14,33 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var {actions} = require('app/modules/storedSessionsFilter');
var InputSearch = require('./../inputSearch.jsx');
var {Table, Column, Cell, TextCell, SortHeaderCell, SortTypes, EmptyIndicator} = require('app/components/table.jsx');
var {ButtonCell, SingleUserCell, DateCreatedCell} = require('./listItems');
var {DateRangePicker} = require('./../datePicker.jsx');
var moment = require('moment');
var {isMatch} = require('app/common/objectUtils');
var _ = require('_');
var {displayDateFormat} = require('app/config');
var React = require('react');
var moment = require('moment');
var InputSearch = require('./../inputSearch.jsx');
var { isMatch } = require('app/common/objectUtils');
var { displayDateFormat} = require('app/config');
var { actions } = require('app/modules/storedSessionsFilter');
var { Table, Column, Cell, TextCell, SortHeaderCell, SortTypes, EmptyIndicator } = require('app/components/table.jsx');
var { ButtonCell, SingleUserCell, DateCreatedCell } = require('./listItems');
var { DateRangePicker } = require('./../datePicker.jsx');
var ArchivedSessions = React.createClass({
getInitialState(){
this.searchableProps = ['serverIp', 'created', 'sid', 'login'];
this.searchableProps = ['clientIp', 'nodeIp', 'created', 'sid', 'login'];
return { filter: '', colSortDirs: {created: 'ASC'}};
},
componentWillMount(){
setTimeout(actions.fetch, 0);
this.refreshInterval = setInterval(actions.fetch, 2500);
},
componentWillUnmount(){
clearInterval(this.refreshInterval);
},
onFilterChange(value){
this.state.filter = value;
this.setState(this.state);
@ -85,7 +77,7 @@ var ArchivedSessions = React.createClass({
return sorted;
},
render: function() {
render() {
let { start, end } = this.props.filter;
let data = this.props.data.filter(
item => !item.active && moment(item.created).isBetween(start, end));
@ -95,10 +87,10 @@ var ArchivedSessions = React.createClass({
return (
<div className="grv-sessions-stored">
<div className="grv-header">
<div className="grv-flex">
<div className="grv-flex m-b-md">
<div className="grv-flex-column"></div>
<div className="grv-flex-column">
<h1> Archived Sessions </h1>
<h2 className="text-center"> Archived Sessions </h2>
</div>
<div className="grv-flex-column">
<InputSearch value={this.filter} onChange={this.onFilterChange}/>
@ -114,22 +106,31 @@ var ArchivedSessions = React.createClass({
</div>
</div>
</div>
<div className="grv-content">
{data.length === 0 ? <EmptyIndicator text="No matching archived sessions found."/> :
<div className="">
<Table rowCount={data.length} className="table-striped">
<Column
columnKey="sid"
header={<Cell> Session ID </Cell> }
cell={<TextCell data={data}/> }
/>
<Column
header={<Cell/>}
cell={
<ButtonCell data={data} />
}
/>
<Column
columnKey="nodeIp"
header={<Cell> Node IP </Cell> }
cell={<TextCell data={data} /> }
/>
<Column
columnKey="clientIp"
header={<Cell> Client IP </Cell> }
cell={<TextCell data={data} /> }
/>
<Column
columnKey="sid"
header={<Cell> Session ID </Cell> }
cell={<TextCell data={data}/> }
/>
<Column
columnKey="created"
header={

View file

@ -0,0 +1,41 @@
/*
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
let Timer = React.createClass({
shouldComponentUpdate(){
return false;
},
componentWillMount(){
let { onTimeout, interval=2500 } = this.props;
onTimeout();
this.refreshInterval = setInterval(onTimeout, interval);
},
componentWillUnmount(){
clearInterval(this.refreshInterval);
},
render() {
return null
}
});
module.exports = Timer;

View file

@ -15,8 +15,9 @@ limitations under the License.
*/
var reactor = require('app/reactor');
var {fetchActiveSessions} = require('./../sessions/actions');
var {fetchNodes} = require('./../nodes/actions');
var { fetchActiveSessions } = require('./../sessions/actions');
var { fetchNodes } = require('./../nodes/actions');
var { logout } = require('app/services/auth');
var $ = require('jQuery');
const { TLPT_APP_INIT, TLPT_APP_FAILED, TLPT_APP_READY } = require('./actionTypes');
@ -24,12 +25,24 @@ const { TLPT_APP_INIT, TLPT_APP_FAILED, TLPT_APP_READY } = require('./actionType
const actions = {
initApp() {
reactor.dispatch(TLPT_APP_INIT);
reactor.dispatch(TLPT_APP_INIT);
actions.fetchNodesAndSessions()
.done(()=> reactor.dispatch(TLPT_APP_READY) )
.fail(()=> reactor.dispatch(TLPT_APP_FAILED) );
},
checkIfUserLoggedIn(){
/*
* lets query for nodes as a checker for a valid user session, in case of 403
* make a redirect to a login page (in case if a server got restarted).
*/
fetchNodes().fail(err => {
if(err.status == 403){
logout();
}
});
},
fetchNodesAndSessions() {
return $.when(fetchNodes(), fetchActiveSessions());
}

View file

@ -24,7 +24,7 @@ const logger = require('app/common/logger').create('Modules/Nodes');
export default {
fetchNodes(){
api.get(cfg.api.nodesPath).done((data=[])=>{
return api.get(cfg.api.nodesPath).done((data=[])=>{
var nodeArray = data.nodes.map(item=>item.node);
reactor.dispatch(TLPT_NODES_RECEIVE, nodeArray);
}).fail((err)=>{

View file

@ -19,12 +19,12 @@ var { TLPT_NOTIFICATIONS_ADD } = require('./actionTypes');
export default {
showError(text, title='ERROR'){
dispatch({isError: true, text: text, title});
showError(title='Error'){
dispatch({isError: true, title});
},
showSuccess(text, title='SUCCESS'){
dispatch({isSuccess:true, text: text, title});
showSuccess(title='SUCCESS'){
dispatch({isSuccess:true, title});
},
showInfo(text, title='INFO'){

View file

@ -60,7 +60,7 @@ function createView(session){
var parties = reactor.evaluate(partiesBySessionId(sid));
if(parties.length > 0){
serverIp = parties[0].serverIp;
serverIp = parties[0].serverIp;
}
return {
@ -68,6 +68,8 @@ function createView(session){
sessionUrl: cfg.getActiveSessionRouteUrl(sid),
serverIp,
serverId: session.get('server_id'),
clientIp: session.get('clientIp'),
nodeIp: session.get('nodeIp'),
active: session.get('active'),
created: session.get('created'),
lastActive: session.get('last_active'),
@ -79,7 +81,6 @@ function createView(session){
}
export default {
partiesBySessionId,
sessionsByServer,
sessionsView,
sessionViewById,

View file

@ -44,6 +44,11 @@ function removeStoredSessions(state){
});
}
function parseIp(ip){
ip = ip || '';
return ip.split(':')[0];
}
function updateSessionWithEvents(state, events){
return state.withMutations(state => {
events.forEach(item=>{
@ -57,8 +62,12 @@ function updateSessionWithEvents(state, events){
// check if record already exists
let session = state.get(item.sid);
if(!session){
session = { id: item.sid };
if(!session){
session = {
nodeIp: parseIp(item['addr.local']),
clientIp: parseIp(item['addr.remote']),
id: item.sid
};
}else{
session = session.toJS();
}

View file

@ -27,7 +27,7 @@ const { TLPT_SESSINS_REMOVE_STORED } = require('./../sessions/actionTypes');
const actions = {
fetch(){
fetchStoredSession(){
let { start, end } = reactor.evaluate(filter);
_fetch(start, end);
},

View file

@ -22,5 +22,3 @@ limitations under the License.
font-weight: normal;
font-style: normal;
}
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");

View file

@ -23,11 +23,11 @@ limitations under the License.
text-align: initial;
position: relative;
margin: 15px 0 0 0;
padding: 0 0 0 70px;
padding: 0 0 0 60px;
.grv-icon-google-auth{
position: absolute;
left: 15px;
left: 0px;
height: 40px;
width: 40px;
background-size: contain;

View file

@ -28,7 +28,7 @@ limitations under the License.
}
.grv-content{
padding: 15px;
padding: 10px;
width: 600px;
z-index: 100;
margin: 0 auto;

View file

@ -26,7 +26,7 @@ limitations under the License.
}
.grv-content {
padding: 15px;
padding: 10px;
max-width: 350px;
z-index: 100;
margin: 10px auto 0;
@ -48,10 +48,10 @@ limitations under the License.
text-align: initial;
position: relative;
margin: 15px 0 0;
padding: 0 0 0 70px;
padding: 0 0 0 60px;
.fa-question {
position: absolute;
left: 20px;
left: 7px;
font-size: 40px;
}
}

View file

@ -16,16 +16,8 @@ limitations under the License.
.grv-nodes{
h1{
text-align: center;
}
.grv-search{
margin-top: 20px;
}
.grv-header{
align-items: center;
align-items: center;
}
.grv-nodes-table{

View file

@ -37,10 +37,13 @@ limitations under the License.
.grv-sessions-stored{
flex: 1;
.grv-header{
margin-top: 20px;
margin-bottom: 20px;
h1{
margin-bottom: 30px;
h2{
margin: 0;
}
.grv-flex{
@ -50,7 +53,6 @@ limitations under the License.
.grv-datepicker {
margin: auto;
}
}
.grv-footer{

View file

@ -1,7 +1,7 @@
body {
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: #2f4050;
font-size: 13px;
font-size: 14px;
color: $text-color;
overflow-x: hidden;

View file

@ -61,7 +61,7 @@
box-shadow: 0 0 3px rgba(86, 96, 117, 0.7);
display: none;
float: left;
font-size: 12px;
font-size: 13px;
left: 0;
list-style: none outside none;
padding: 0;
@ -142,7 +142,7 @@
}
.nav-header {
padding: 33px 25px;
padding: 33px 25px;
}
.pace-done .nav-header {

View file

@ -1,5 +1,5 @@
// Basic Colors
$navy: #1ab394; // Primary color
$navy: #4e988b; // Primary color
$dark-gray: #c2c2c2; // Default color
$blue: #1c84c6; // Success color
$lazur: #23c6c8; // Info color