mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 10:13:21 +00:00
(web) open a terminal with an URL
This commit is contained in:
parent
e76ca9ca87
commit
8a9b23e28d
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
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');
|
||||
var reactor = require('app/reactor');
|
||||
var {nodeHostNameByServerId} = require('app/modules/nodes/getters');
|
||||
var SessionLeftPanel = require('./sessionLeftPanel');
|
||||
var cfg = require('app/config');
|
||||
var session = require('app/services/session');
|
||||
var Terminal = require('app/common/term/terminal');
|
||||
var {updateSessionFromEventStream} = require('app/modules/currentSession/actions');
|
||||
|
||||
var ActiveSession = React.createClass({
|
||||
render() {
|
||||
let {login, parties, serverId} = this.props;
|
||||
let serverLabelText = '';
|
||||
if(serverId){
|
||||
let hostname = reactor.evaluate(nodeHostNameByServerId(serverId));
|
||||
serverLabelText = `${login}@${hostname}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grv-current-session">
|
||||
<SessionLeftPanel parties={parties}/>
|
||||
<div className="grv-current-session-server-info">
|
||||
<h3>{serverLabelText}</h3>
|
||||
</div>
|
||||
<TtyTerminal ref="ttyCmntInstance" {...this.props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TtyTerminal = React.createClass({
|
||||
componentDidMount() {
|
||||
let { serverId, siteId, login, sid } = this.props;
|
||||
let {token} = session.getUserData();
|
||||
let url = cfg.api.getSiteUrl(siteId);
|
||||
|
||||
let options = {
|
||||
tty: {
|
||||
serverId, login, sid, token, url
|
||||
},
|
||||
el: this.refs.container
|
||||
}
|
||||
|
||||
this.terminal = new Terminal(options);
|
||||
this.terminal.ttyEvents.on('data', updateSessionFromEventStream(siteId));
|
||||
this.terminal.open();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.terminal.destroy();
|
||||
},
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
},
|
||||
|
||||
render() {
|
||||
return ( <div ref="container"> </div> );
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ActiveSession;
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import reactor from 'app/reactor';
|
||||
import { getters } from 'app/modules/currentSession/';
|
||||
import SessionPlayer from './sessionPlayer.jsx';
|
||||
import ActiveSession from './activeSession.jsx';
|
||||
import cfg from 'app/config';
|
||||
import { initSession } from 'app/modules/currentSession/actions';
|
||||
|
||||
const CurrentSessionHost = React.createClass({
|
||||
|
||||
mixins: [reactor.ReactMixin],
|
||||
|
||||
getDataBindings() {
|
||||
return {
|
||||
currentSession: getters.currentSession
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => initSession(this.props.params.sid), 0);
|
||||
},
|
||||
|
||||
render() {
|
||||
let { currentSession } = this.state;
|
||||
if(!currentSession){
|
||||
return null;
|
||||
}
|
||||
|
||||
if(currentSession.active){
|
||||
return <ActiveSession {...currentSession}/>;
|
||||
}
|
||||
|
||||
let { sid, siteId } = currentSession;
|
||||
let url = cfg.api.getFetchSessionUrl({ siteId, sid });
|
||||
|
||||
return <SessionPlayer url={url}/>;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default CurrentSessionHost;
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
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');
|
||||
var {actions} = require('app/modules/currentSession/');
|
||||
var {UserIcon} = require('./../icons.jsx');
|
||||
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
|
||||
|
||||
const SessionLeftPanel = ({parties}) => {
|
||||
parties = parties || [];
|
||||
let userIcons = parties.map((item, index)=>(
|
||||
<li key={index} className="animated"><UserIcon colorIndex={index} isDark={true} name={item.user}/></li>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="grv-terminal-participans">
|
||||
<ul className="nav">
|
||||
<li title="Close">
|
||||
<button onClick={actions.close} className="btn btn-danger btn-circle" type="button">
|
||||
<span>✖</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{ userIcons.length > 0 ? <hr className="grv-divider"/> : null }
|
||||
<ReactCSSTransitionGroup className="nav" component='ul'
|
||||
transitionEnterTimeout={500}
|
||||
transitionLeaveTimeout={500}
|
||||
transitionName={{
|
||||
enter: "fadeIn",
|
||||
leave: "fadeOut"
|
||||
}}>
|
||||
{userIcons}
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
module.exports = SessionLeftPanel;
|
|
@ -1,15 +1,33 @@
|
|||
import React from 'react';
|
||||
|
||||
const WHEN_TO_DISPLAY = 100; // 0.2s;
|
||||
|
||||
class Indicator extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._timer = null;
|
||||
this.state = {
|
||||
canDisplay: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._timer = setTimeout(() => {
|
||||
this.setState({
|
||||
canDisplay: true
|
||||
})
|
||||
}, WHEN_TO_DISPLAY);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { enabled = true, type } = this.props;
|
||||
|
||||
if (!enabled) {
|
||||
let { type } = this.props;
|
||||
|
||||
if (!this.state.canDisplay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import actions from 'app/modules/terminal/actions';
|
||||
import {UserIcon} from './../icons.jsx';
|
||||
import {UserIcon} from './icons.jsx';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
|
||||
const SessionLeftPanel = ({parties}) => {
|
||||
const PartyListPanel = ({parties, onClose}) => {
|
||||
parties = parties || [];
|
||||
let userIcons = parties.map((item, index)=>(
|
||||
<li key={index} className="animated"><UserIcon colorIndex={index} isDark={true} name={item.user}/></li>
|
||||
|
@ -29,7 +28,7 @@ const SessionLeftPanel = ({parties}) => {
|
|||
<div className="grv-terminal-participans">
|
||||
<ul className="nav">
|
||||
<li title="Close">
|
||||
<button onClick={actions.close} className="btn btn-danger btn-circle" type="button">
|
||||
<button onClick={onClose} className="btn btn-danger btn-circle" type="button">
|
||||
<span>✖</span>
|
||||
</button>
|
||||
</li>
|
||||
|
@ -48,4 +47,4 @@ const SessionLeftPanel = ({parties}) => {
|
|||
)
|
||||
};
|
||||
|
||||
module.exports = SessionLeftPanel;
|
||||
export default PartyListPanel;
|
|
@ -13,45 +13,59 @@ 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.
|
||||
*/
|
||||
import $ from 'jQuery';
|
||||
import initScroll from 'perfect-scrollbar/jquery';
|
||||
import React from 'react';
|
||||
import { connect } from 'nuclear-js-react-addons';
|
||||
import ReactSlider from 'react-slider';
|
||||
import getters from 'app/modules/player/getters';
|
||||
import Terminal from 'app/common/term/terminal';
|
||||
import { TtyPlayer } from 'app/common/term/ttyPlayer';
|
||||
import { initPlayer, close } from 'app/modules/player/actions';
|
||||
import Indicator from './../indicator.jsx';
|
||||
import PartyListPanel from './../partyListPanel';
|
||||
|
||||
var React = require('react');
|
||||
var ReactSlider = require('react-slider');
|
||||
var {TtyPlayer} = require('app/common/term/ttyPlayer')
|
||||
var Terminal = require('app/common/term/terminal');
|
||||
var SessionLeftPanel = require('./sessionLeftPanel.jsx');
|
||||
var $ = require('jQuery');
|
||||
require('perfect-scrollbar/jquery')($);
|
||||
initScroll($);
|
||||
|
||||
class Term extends Terminal{
|
||||
constructor(tty, el){
|
||||
super({el, scrollBack: 0});
|
||||
this.tty = tty;
|
||||
const PlayerHost = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => initPlayer(this.props.params), 0);
|
||||
},
|
||||
|
||||
render() {
|
||||
let { store } = this.props;
|
||||
if(store.isReady()){
|
||||
let url = store.getStoredSessionUrl();
|
||||
return <Player url={url}/>;
|
||||
}
|
||||
|
||||
let $indicator = null;
|
||||
|
||||
if(store.isLoading()){
|
||||
$indicator = (<Indicator type="bounce" />);
|
||||
}
|
||||
|
||||
if(store.isError()){
|
||||
$indicator = (<ErrorIndicator text={store.getErrorText()} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>{$indicator}</Box>
|
||||
);
|
||||
}
|
||||
|
||||
connect(){
|
||||
this.tty.connect();
|
||||
});
|
||||
|
||||
function mapStateToProps() {
|
||||
return {
|
||||
store: getters.store
|
||||
}
|
||||
|
||||
open() {
|
||||
super.open();
|
||||
$(this._el).perfectScrollbar();
|
||||
}
|
||||
|
||||
resize(cols, rows) {
|
||||
if(cols === this.cols && rows === this.rows){
|
||||
return;
|
||||
}
|
||||
|
||||
super.resize(cols, rows);
|
||||
$(this._el).perfectScrollbar('update');
|
||||
}
|
||||
|
||||
_disconnect(){}
|
||||
|
||||
_requestResize(){}
|
||||
}
|
||||
|
||||
var SessionPlayer = React.createClass({
|
||||
export default connect(mapStateToProps)(PlayerHost);
|
||||
|
||||
const Player = React.createClass({
|
||||
calculateState(){
|
||||
return {
|
||||
length: this.tty.length,
|
||||
|
@ -114,10 +128,9 @@ var SessionPlayer = React.createClass({
|
|||
var {isPlaying, time} = this.state;
|
||||
|
||||
return (
|
||||
<div className="grv-current-session grv-session-player">
|
||||
<SessionLeftPanel/>
|
||||
<div ref="container"/>
|
||||
<div className="grv-session-player-controls">
|
||||
<Box>
|
||||
<div ref="container"/>
|
||||
<div className="grv-session-player-controls">
|
||||
<button className="btn" onClick={this.togglePlayStop}>
|
||||
{ isPlaying ? <i className="fa fa-stop"></i> : <i className="fa fa-play"></i> }
|
||||
</button>
|
||||
|
@ -131,11 +144,55 @@ var SessionPlayer = React.createClass({
|
|||
defaultValue={1}
|
||||
withBars
|
||||
className="grv-slider" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default SessionPlayer;
|
||||
class Term extends Terminal{
|
||||
constructor(tty, el){
|
||||
super({el, scrollBack: 0});
|
||||
this.tty = tty;
|
||||
}
|
||||
|
||||
connect(){
|
||||
this.tty.connect();
|
||||
}
|
||||
|
||||
open() {
|
||||
super.open();
|
||||
$(this._el).perfectScrollbar();
|
||||
}
|
||||
|
||||
resize(cols, rows) {
|
||||
if(cols === this.cols && rows === this.rows){
|
||||
return;
|
||||
}
|
||||
|
||||
super.resize(cols, rows);
|
||||
$(this._el).perfectScrollbar('update');
|
||||
}
|
||||
|
||||
_disconnect(){}
|
||||
|
||||
_requestResize(){}
|
||||
}
|
||||
|
||||
const Box = props => (
|
||||
<div className="grv-terminalhost grv-session-player">
|
||||
<PartyListPanel onClose={close} />
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const ErrorIndicator = ({ text }) => (
|
||||
<div className="grv-terminalhost-indicator-error">
|
||||
<i className="fa fa-exclamation-triangle fa-3x text-warning"></i>
|
||||
<div className="m-l">
|
||||
<strong>Error</strong>
|
||||
<div className="text-center"><small>{text}</small></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
|
@ -17,21 +17,24 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import reactor from 'app/reactor';
|
||||
import cfg from 'app/config';
|
||||
import SessionLeftPanel from './terminalHostPanel';
|
||||
import PartyListPanel from './../partyListPanel';
|
||||
import session from 'app/services/session';
|
||||
import Terminal from 'app/common/term/terminal';
|
||||
import getters from 'app/modules/terminal/getters';
|
||||
import { updateSessionFromEventStream } from 'app/modules/terminal/actions';
|
||||
import termGetters from 'app/modules/terminal/getters';
|
||||
import sessionGetters from 'app/modules/sessions/getters';
|
||||
import Indicator from './../indicator.jsx';
|
||||
import { initTerminal } from 'app/modules/terminal/actions';
|
||||
import { initTerminal, startNew, close, updateSessionFromEventStream } from 'app/modules/terminal/actions';
|
||||
import { openPlayer } from 'app/modules/player/actions';
|
||||
|
||||
const TerminalHost = React.createClass({
|
||||
|
||||
mixins: [reactor.ReactMixin],
|
||||
|
||||
getDataBindings() {
|
||||
let { sid } = this.props.routeParams;
|
||||
return {
|
||||
store: getters.store
|
||||
store: termGetters.store,
|
||||
parties: sessionGetters.activePartiesById(sid)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -39,10 +42,18 @@ const TerminalHost = React.createClass({
|
|||
setTimeout(() => initTerminal(this.props.routeParams), 0);
|
||||
},
|
||||
|
||||
startNew() {
|
||||
startNew(this.props.routeParams);
|
||||
},
|
||||
|
||||
replay() {
|
||||
openPlayer(this.props.routeParams);
|
||||
},
|
||||
|
||||
render() {
|
||||
let { store } = this.state;
|
||||
let { store, parties } = this.state;
|
||||
let serverLabel = store.getServerLabel();
|
||||
let { parties = [], status, ...props } = store.toJS();
|
||||
let { status, ...props } = store.toJS();
|
||||
|
||||
let $content = null;
|
||||
|
||||
|
@ -59,16 +70,19 @@ const TerminalHost = React.createClass({
|
|||
}
|
||||
|
||||
if (status.isNotFound) {
|
||||
$content = (<SidNotFoundError />);
|
||||
$content = (
|
||||
<SidNotFoundError
|
||||
onReplay={this.replay}
|
||||
onNew={this.startNew} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grv-current-session">
|
||||
<SessionLeftPanel parties={parties}/>
|
||||
<div className="grv-current-session-server-info">
|
||||
<h3>{serverLabel}</h3>
|
||||
</div>
|
||||
{$content}
|
||||
<div className="grv-terminalhost">
|
||||
<PartyListPanel parties={parties} onClose={close}/>
|
||||
<div className="grv-terminalhost-server-info">
|
||||
<h3>{serverLabel}</h3>
|
||||
</div>
|
||||
{$content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -77,7 +91,7 @@ const TerminalHost = React.createClass({
|
|||
const TtyTerminal = React.createClass({
|
||||
componentDidMount() {
|
||||
let { serverId, siteId, login, sid } = this.props;
|
||||
let {token} = session.getUserData();
|
||||
let { token } = session.getUserData();
|
||||
let url = cfg.api.getSiteUrl(siteId);
|
||||
|
||||
let options = {
|
||||
|
@ -115,13 +129,13 @@ const ErrorIndicator = ({ text }) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const SidNotFoundError = () => (
|
||||
const SidNotFoundError = ({onNew, onReplay}) => (
|
||||
<div className="grv-terminalhost-indicator-error">
|
||||
<div className="text-center">
|
||||
<strong>The session is no longer active</strong>
|
||||
<div className="m-t">
|
||||
<button className="btn btn-sm btn-primary m-r"> Start New </button>
|
||||
<button className="btn btn-sm btn-primary"> Replay </button>
|
||||
<button onClick={onNew} className="btn btn-sm btn-primary m-r"> Start New </button>
|
||||
<button onClick={onReplay} className="btn btn-sm btn-primary"> Replay </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,7 +39,8 @@ let cfg = {
|
|||
newUser: '/web/newuser/:inviteToken',
|
||||
msgs: '/web/msg/:type(/:subType)',
|
||||
pageNotFound: '/web/notfound',
|
||||
terminal: '/web/cluster/:siteId/node/:serverId/:login(/:sid)'
|
||||
terminal: '/web/cluster/:siteId/node/:serverId/:login(/:sid)',
|
||||
player: '/web/player/node/:siteId/sid/:sid'
|
||||
},
|
||||
|
||||
api: {
|
||||
|
@ -107,7 +108,10 @@ let cfg = {
|
|||
getU2fCreateUserChallengeUrl(inviteToken){
|
||||
return formatPattern(cfg.api.u2fCreateUserChallengePath, {inviteToken});
|
||||
}
|
||||
},
|
||||
|
||||
getPlayerUrl({siteId, serverId, sid}) {
|
||||
return formatPattern(cfg.routes.player, { siteId, serverId, sid });
|
||||
},
|
||||
|
||||
getTerminalLoginUrl({siteId, serverId, login, sid}) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import Signup from './components/user/invite.jsx';
|
|||
import Nodes from './components/nodes/main.jsx';
|
||||
import Sessions from './components/sessions/main.jsx';
|
||||
import TerminalHost from './components/terminal/terminalHost.jsx';
|
||||
import PlayerHost from './components/player/playerHost.jsx';
|
||||
import { MessagePage, NotFound } from './components/msgPage.jsx';
|
||||
import { ensureUser } from './modules/user/actions';
|
||||
import { initApp } from './modules/app/actions';
|
||||
|
@ -48,8 +49,8 @@ render((
|
|||
<Route path={cfg.routes.app} onEnter={initApp} >
|
||||
<Route path={cfg.routes.sessions} component={Sessions}/>
|
||||
<Route path={cfg.routes.nodes} component={Nodes}/>
|
||||
<Route path={cfg.routes.terminal}
|
||||
components={{ CurrentSessionHost: TerminalHost }} />
|
||||
<Route path={cfg.routes.terminal} components={{ CurrentSessionHost: TerminalHost }} />
|
||||
<Route path={cfg.routes.player} components={{ CurrentSessionHost: PlayerHost }} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" component={NotFound} />
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import reactor from 'app/reactor';
|
||||
import session from 'app/services/session';
|
||||
import api from 'app/services/api';
|
||||
import cfg from 'app/config';
|
||||
import getters from './getters';
|
||||
import { fetchStoredSession, updateSession } from './../sessions/actions';
|
||||
import sessionGetters from './../sessions/getters';
|
||||
import {showError} from 'app/modules/notifications/actions';
|
||||
|
||||
const logger = require('app/common/logger').create('Current Session');
|
||||
|
||||
const { TLPT_CURRENT_SESSION_OPEN, TLPT_CURRENT_SESSION_CLOSE } = require('./actionTypes');
|
||||
|
||||
const actions = {
|
||||
|
||||
createNewSession(siteId, serverId, login) {
|
||||
let data = {
|
||||
session: {
|
||||
terminal_params: {
|
||||
w: 45,
|
||||
h: 5
|
||||
},
|
||||
login
|
||||
}
|
||||
}
|
||||
|
||||
api.post(cfg.api.getSiteSessionUrl(siteId), data).then(json => {
|
||||
let history = session.getHistory();
|
||||
let sid = json.session.id;
|
||||
let routeUrl = cfg.getCurrentSessionRouteUrl({
|
||||
siteId,
|
||||
sid
|
||||
});
|
||||
|
||||
reactor.dispatch(TLPT_CURRENT_SESSION_OPEN, {
|
||||
siteId,
|
||||
serverId,
|
||||
login,
|
||||
sid,
|
||||
active: true,
|
||||
isNew: true
|
||||
});
|
||||
|
||||
history.push(routeUrl);
|
||||
});
|
||||
},
|
||||
|
||||
initSession(sid) {
|
||||
logger.info('attempt to open session', { sid });
|
||||
|
||||
// check if terminal is already open
|
||||
let currentSession = reactor.evaluate(getters.currentSession);
|
||||
if (currentSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
// look up active session matching given sid
|
||||
let activeSession = reactor.evaluate(sessionGetters.activeSessionById(sid));
|
||||
if (activeSession) {
|
||||
let { server_id, login, siteId, id } = activeSession;
|
||||
reactor.dispatch(TLPT_CURRENT_SESSION_OPEN, {
|
||||
login,
|
||||
siteId,
|
||||
sid: id,
|
||||
serverId: server_id,
|
||||
active: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// stored session then...
|
||||
fetchStoredSession(sid)
|
||||
.done(() => {
|
||||
let storedSession = reactor.evaluate(sessionGetters.storedSessionById(sid));
|
||||
if (!storedSession) {
|
||||
// TODO: display not found page
|
||||
showError('Cannot find archived session');
|
||||
return;
|
||||
}
|
||||
|
||||
let { siteId } = storedSession;
|
||||
reactor.dispatch(TLPT_CURRENT_SESSION_OPEN, {
|
||||
siteId,
|
||||
sid
|
||||
});
|
||||
|
||||
})
|
||||
.fail(err => {
|
||||
let msg = err.responseJSON ? err.responseJSON.message : '';
|
||||
showError('Unable to fetch archived session', msg);
|
||||
logger.error('open session', err);
|
||||
})
|
||||
},
|
||||
|
||||
close() {
|
||||
let { isNew } = reactor.evaluate(getters.currentSession);
|
||||
|
||||
reactor.dispatch(TLPT_CURRENT_SESSION_CLOSE);
|
||||
|
||||
if (isNew) {
|
||||
session.getHistory().push(cfg.routes.nodes);
|
||||
} else {
|
||||
session.getHistory().push(cfg.routes.sessions);
|
||||
}
|
||||
},
|
||||
|
||||
updateSessionFromEventStream(siteId) {
|
||||
return data => {
|
||||
data.events.forEach(item => {
|
||||
if (item.event === 'session.end') {
|
||||
actions.close();
|
||||
}
|
||||
})
|
||||
|
||||
updateSession({
|
||||
siteId: siteId,
|
||||
json: data.session
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default actions;
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
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 { Store, toImmutable } = require('nuclear-js');
|
||||
var { TLPT_CURRENT_SESSION_OPEN, TLPT_CURRENT_SESSION_CLOSE } = require('./actionTypes');
|
||||
|
||||
export default Store({
|
||||
getInitialState() {
|
||||
return toImmutable(null);
|
||||
},
|
||||
|
||||
initialize() {
|
||||
this.on(TLPT_CURRENT_SESSION_OPEN, setCurrentSession);
|
||||
this.on(TLPT_CURRENT_SESSION_CLOSE, close);
|
||||
}
|
||||
})
|
||||
|
||||
function close(){
|
||||
return toImmutable(null);
|
||||
}
|
||||
|
||||
function setCurrentSession(state, {siteId, serverId, login, sid, active, isNew} ){
|
||||
return toImmutable({
|
||||
siteId,
|
||||
serverId,
|
||||
login,
|
||||
sid,
|
||||
active,
|
||||
isNew
|
||||
});
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
const currentSession = [ ['tlpt_current_session'], ['tlpt_sessions_active'],
|
||||
(current, sessions) => {
|
||||
if(!current){
|
||||
return null;
|
||||
}
|
||||
|
||||
let curSession = current.toJS();
|
||||
|
||||
// get the list of participants
|
||||
if(sessions.has(curSession.sid)){
|
||||
let activeSessionRec = sessions.get(curSession.sid);
|
||||
curSession.parties = activeSessionRec.parties.toJS();
|
||||
}
|
||||
|
||||
return curSession;
|
||||
}
|
||||
];
|
||||
|
||||
export default {
|
||||
currentSession
|
||||
}
|
|
@ -16,12 +16,12 @@ limitations under the License.
|
|||
|
||||
import reactor from 'app/reactor';
|
||||
import termStore from './terminal/store';
|
||||
|
||||
import playerStore from './player/store';
|
||||
|
||||
reactor.registerStores({
|
||||
'tlpt': require('./app/appStore'),
|
||||
'tlpt_current_session': require('./currentSession/currentSessionStore'),
|
||||
'tlpt': require('./app/appStore'),
|
||||
'tlpt_terminal': termStore,
|
||||
'tlpt_player': playerStore,
|
||||
'tlpt_user': require('./user/userStore'),
|
||||
'tlpt_sites': require('./sites/siteStore'),
|
||||
'tlpt_user_invite': require('./user/userInviteStore'),
|
||||
|
|
|
@ -15,7 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
import keyMirror from 'keymirror'
|
||||
|
||||
export default keyMirror({
|
||||
TLPT_CURRENT_SESSION_OPEN: null,
|
||||
TLPT_CURRENT_SESSION_CLOSE: null
|
||||
export default keyMirror({
|
||||
TLPT_PLAYER_INIT: null,
|
||||
TLPT_PLAYER_CLOSE: null,
|
||||
TLPT_PLAYER_SET_STATUS: null
|
||||
})
|
75
web/src/app/modules/player/actions.js
Normal file
75
web/src/app/modules/player/actions.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import reactor from 'app/reactor';
|
||||
import session from 'app/services/session';
|
||||
import api from 'app/services/api';
|
||||
import cfg from 'app/config';
|
||||
import { fetchStoredSession } from './../sessions/actions';
|
||||
import sessionGetters from './../sessions/getters';
|
||||
|
||||
const logger = require('app/common/logger').create('app/flux/player');
|
||||
|
||||
import {
|
||||
TLPT_PLAYER_INIT,
|
||||
TLPT_PLAYER_CLOSE,
|
||||
TLPT_PLAYER_SET_STATUS
|
||||
} from './actionTypes';
|
||||
|
||||
const actions = {
|
||||
|
||||
openPlayer(routeParams) {
|
||||
let routeUrl = cfg.getPlayerUrl(routeParams);
|
||||
session.getHistory().push(routeUrl);
|
||||
},
|
||||
|
||||
initPlayer(routeParams) {
|
||||
logger.info('initPlayer()', routeParams);
|
||||
let {sid, siteId } = routeParams;
|
||||
reactor.dispatch(TLPT_PLAYER_SET_STATUS, { isLoading: true });
|
||||
fetchStoredSession(sid, siteId)
|
||||
.done(() => {
|
||||
let storedSession = reactor.evaluate(sessionGetters.storedSessionById(sid));
|
||||
if (!storedSession) {
|
||||
reactor.dispatch(TLPT_PLAYER_SET_STATUS, {
|
||||
isError: true,
|
||||
errorText: 'Cannot find archived session'
|
||||
});
|
||||
} else {
|
||||
let { siteId } = storedSession;
|
||||
reactor.dispatch(TLPT_PLAYER_INIT, {
|
||||
siteId,
|
||||
sid
|
||||
});
|
||||
}
|
||||
})
|
||||
.fail(err => {
|
||||
logger.error('open session', err);
|
||||
let errorText = api.getErrorText(err);
|
||||
reactor.dispatch(TLPT_PLAYER_SET_STATUS, {
|
||||
isError: true,
|
||||
errorText,
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
close() {
|
||||
reactor.dispatch(TLPT_PLAYER_CLOSE);
|
||||
session.getHistory().push(cfg.routes.sessions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default actions;
|
|
@ -13,6 +13,7 @@ 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.
|
||||
*/
|
||||
module.exports.getters = require('./getters');
|
||||
module.exports.actions = require('./actions');
|
||||
module.exports.activeTermStore = require('./currentSessionStore');
|
||||
|
||||
export default {
|
||||
store: ['tlpt_player']
|
||||
}
|
88
web/src/app/modules/player/store.js
Normal file
88
web/src/app/modules/player/store.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
import { Store } from 'nuclear-js';
|
||||
import { Record } from 'immutable';
|
||||
import cfg from 'app/config';
|
||||
|
||||
import {
|
||||
TLPT_PLAYER_INIT,
|
||||
TLPT_PLAYER_CLOSE,
|
||||
TLPT_PLAYER_SET_STATUS
|
||||
} from './actionTypes';
|
||||
|
||||
const PlayerStatusRec = new Record({
|
||||
isReady: false,
|
||||
isLoading: false,
|
||||
isNotFound: false,
|
||||
isError: false,
|
||||
errorText: undefined,
|
||||
})
|
||||
|
||||
export class PlayerRec extends Record({
|
||||
status: new PlayerStatusRec(),
|
||||
siteId: undefined,
|
||||
sid: undefined
|
||||
}) {
|
||||
makeReady() {
|
||||
return this.set('status', new PlayerStatusRec({ isReady: true }));
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.getIn(['status', 'isReady']);
|
||||
}
|
||||
|
||||
isLoading() {
|
||||
return this.getIn(['status', 'isLoading']);
|
||||
}
|
||||
|
||||
isError() {
|
||||
return this.getIn(['status', 'isError']);
|
||||
}
|
||||
|
||||
getErrorText() {
|
||||
return this.getIn(['status', 'errorText']);
|
||||
}
|
||||
|
||||
getStoredSessionUrl() {
|
||||
return cfg.api.getFetchSessionUrl({
|
||||
siteId: this.siteId,
|
||||
sid: this.sid
|
||||
});
|
||||
}
|
||||
}
|
||||
export default Store({
|
||||
getInitialState() {
|
||||
return new PlayerRec();
|
||||
},
|
||||
|
||||
initialize() {
|
||||
this.on(TLPT_PLAYER_INIT, init);
|
||||
this.on(TLPT_PLAYER_CLOSE, close);
|
||||
this.on(TLPT_PLAYER_SET_STATUS, changeStatus);
|
||||
}
|
||||
})
|
||||
|
||||
function close(){
|
||||
return new PlayerRec();
|
||||
}
|
||||
|
||||
function init(state, json){
|
||||
return new PlayerRec(json).makeReady();
|
||||
}
|
||||
|
||||
function changeStatus(state, status) {
|
||||
return state.setIn(['status'], new PlayerStatusRec(status));
|
||||
}
|
|
@ -30,8 +30,8 @@ const {
|
|||
|
||||
const actions = {
|
||||
|
||||
fetchStoredSession(sid) {
|
||||
let siteId = reactor.evaluate(appGetters.siteId);
|
||||
fetchStoredSession(sid, siteId) {
|
||||
siteId = siteId || reactor.evaluate(appGetters.siteId);
|
||||
return api.get(cfg.api.getSessionEventsUrl({ siteId, sid })).then(json=>{
|
||||
if(json && json.events){
|
||||
reactor.dispatch(TLPT_SESSIONS_EVENTS_RECEIVE, { siteId, json: json.events });
|
||||
|
|
|
@ -33,6 +33,16 @@ const storedSessionList = [['tlpt_sessions_archived'], ['tlpt', 'siteId'], (sess
|
|||
|
||||
const activeSessionById = sid => ['tlpt_sessions_active', sid];
|
||||
const storedSessionById = sid => ['tlpt_sessions_archived', sid];
|
||||
|
||||
|
||||
const activePartiesById = sid => [['tlpt_sessions_active', sid, 'parties'], parties => {
|
||||
if (parties) {
|
||||
return parties.toJS();
|
||||
}
|
||||
|
||||
return [];
|
||||
}];
|
||||
|
||||
const nodeIpById = sid => ['tlpt_sessions_events', sid, EventTypeEnum.START, 'addr.local'];
|
||||
|
||||
function createStoredListItem(session){
|
||||
|
@ -42,7 +52,7 @@ function createStoredListItem(session){
|
|||
let nodeDisplayText = getNodeIpDisplayText(server_id, nodeIp);
|
||||
let createdDisplayText = getCreatedDisplayText(created);
|
||||
|
||||
let sessionUrl = cfg.getCurrentSessionRouteUrl({
|
||||
let sessionUrl = cfg.getPlayerUrl({
|
||||
sid,
|
||||
siteId
|
||||
});
|
||||
|
@ -65,15 +75,17 @@ function createActiveListItem(session){
|
|||
let sid = session.get('id');
|
||||
let parties = createParties(session.parties);
|
||||
|
||||
let { siteId, created, last_active, server_id } = session;
|
||||
let { siteId, created, login, last_active, server_id } = session;
|
||||
let duration = moment(last_active).diff(created);
|
||||
let nodeIp = reactor.evaluate(nodeIpById(sid));
|
||||
let nodeDisplayText = getNodeIpDisplayText(server_id, nodeIp);
|
||||
let createdDisplayText = getCreatedDisplayText(created);
|
||||
|
||||
let sessionUrl = cfg.getCurrentSessionRouteUrl({
|
||||
let sessionUrl = cfg.getTerminalLoginUrl({
|
||||
sid,
|
||||
siteId
|
||||
siteId,
|
||||
login,
|
||||
serverId: server_id
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -121,6 +133,7 @@ export default {
|
|||
storedSessionList,
|
||||
activeSessionList,
|
||||
activeSessionById,
|
||||
activePartiesById,
|
||||
storedSessionById,
|
||||
createStoredListItem
|
||||
}
|
||||
}
|
|
@ -26,8 +26,23 @@ const logger = require('app/common/logger').create('Current Session');
|
|||
|
||||
const { TLPT_TERMINAL_OPEN, TLPT_TERMINAL_CLOSE, TLPT_TERMINAL_SET_STATUS } = require('./actionTypes');
|
||||
|
||||
const changeBrowserUrl = newRouteParams => {
|
||||
let routeUrl = cfg.getTerminalLoginUrl(newRouteParams);
|
||||
session.getHistory().push(routeUrl);
|
||||
}
|
||||
|
||||
const actions = {
|
||||
|
||||
startNew(routeParams) {
|
||||
let newRouteParams = {
|
||||
...routeParams,
|
||||
sid: undefined
|
||||
}
|
||||
|
||||
changeBrowserUrl(newRouteParams);
|
||||
actions.initTerminal(newRouteParams);
|
||||
},
|
||||
|
||||
createNewSession(routeParams) {
|
||||
let { login, siteId } = routeParams;
|
||||
let data = {
|
||||
|
@ -43,15 +58,14 @@ const actions = {
|
|||
return api.post(cfg.api.getSiteSessionUrl(siteId), data)
|
||||
.then(json => {
|
||||
let sid = json.session.id;
|
||||
let newRoutParams = {
|
||||
let newRouteParams = {
|
||||
...routeParams,
|
||||
sid
|
||||
};
|
||||
|
||||
let routeUrl = cfg.getTerminalLoginUrl(newRoutParams);
|
||||
reactor.dispatch(TLPT_TERMINAL_OPEN, newRoutParams);
|
||||
|
||||
reactor.dispatch(TLPT_TERMINAL_OPEN, newRouteParams);
|
||||
reactor.dispatch(TLPT_TERMINAL_SET_STATUS, { isReady: true });
|
||||
session.getHistory().push(routeUrl);
|
||||
changeBrowserUrl(newRouteParams);
|
||||
})
|
||||
.fail(err => {
|
||||
let errorText = api.getErrorText(err);
|
||||
|
|
|
@ -36,12 +36,19 @@ export class TermRec extends Record({
|
|||
status: TermStatusRec(),
|
||||
login: undefined,
|
||||
siteId: undefined,
|
||||
serverId: undefined
|
||||
serverId: undefined,
|
||||
sid: undefined
|
||||
}) {
|
||||
|
||||
getServerLabel() {
|
||||
let hostname = reactor.evaluate(nodeHostNameByServerId(this.serverId));
|
||||
return `${this.login} @${hostname}`;
|
||||
|
||||
if (hostname && this.login) {
|
||||
return `${this.login}@${hostname}`;
|
||||
}
|
||||
|
||||
return ''
|
||||
|
||||
}
|
||||
}
|
||||
export default Store({
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.grv-current-session{
|
||||
padding: 40px 25px 10px 90px;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: $grv-color-bg-term;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.grv-terminal-participans{
|
||||
margin-left: -70px;
|
||||
width: 50px;
|
||||
position: absolute;
|
||||
|
||||
.grv-divider{
|
||||
background-color: #3B3838;
|
||||
border-color: #3B3838;
|
||||
}
|
||||
|
||||
.nav {
|
||||
text-align: center;
|
||||
|
||||
> li{
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grv-current-session-server-info{
|
||||
height: 30px;
|
||||
margin-top: -35px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
span{
|
||||
margin-left: 5px
|
||||
}
|
||||
|
||||
.btn{
|
||||
padding: 0 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ limitations under the License.
|
|||
position: relative;
|
||||
|
||||
.grv-sessions-stored-details{
|
||||
max-width: 300px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,3 +6,47 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.grv-terminalhost{
|
||||
padding: 40px 25px 10px 90px;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: $grv-color-bg-term;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.grv-terminal-participans{
|
||||
margin-left: -70px;
|
||||
width: 50px;
|
||||
position: absolute;
|
||||
|
||||
.grv-divider{
|
||||
background-color: #3B3838;
|
||||
border-color: #3B3838;
|
||||
}
|
||||
|
||||
.nav {
|
||||
text-align: center;
|
||||
|
||||
> li{
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grv-terminalhost-server-info{
|
||||
height: 30px;
|
||||
margin-top: -35px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
span{
|
||||
margin-left: 5px
|
||||
}
|
||||
|
||||
.btn{
|
||||
padding: 0 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ limitations under the License.
|
|||
@import "grv-nodes";
|
||||
@import "grv-sessions";
|
||||
@import "grv-nav";
|
||||
@import "grv-current-session";
|
||||
@import "grv-terminalhost";
|
||||
@import "grv-msg-page";
|
||||
@import "grv-slider";
|
||||
|
|
Loading…
Reference in a new issue