Commit 8ef79dd0 authored by charlie-ablett's avatar charlie-ablett

Slanger event subscriptions

parent a55ce476
import * as actions from '../constants/ActionTypes';
import { checkStatus, setAlert } from './actions_helper.js';
import { browserHistory } from 'react-router';
import * as AlertTypes from '../constants/AlertTypes';
import _ from 'lodash';
import settings from '../../settings';
export function getAllUsers(appState) {
const { authToken, tokenType, client, expiry, uid } = appState.session;
const signedIn = (authToken != null);
return(getAllUsersWithTokens(signedIn, authToken, tokenType, client, expiry, uid));
}
export function getAllUsersWithTokens(signedIn, authToken, tokenType, client, expiry, uid) {
return function(dispatch) {
let theResponse;
dispatch({type: actions.GET_ALL_USERS_REQUEST});
fetch(`${settings.apiBaseUrl}/api/admin/users`, {
method: 'GET',
headers: {
'Accept': settings.apiVersionHeader,
'Content-Type': 'application/json',
'Access-Token': authToken,
'Client': client,
'Token-Type': tokenType,
'Expiry': expiry,
'uid': uid
}
})
.then(response => {
theResponse = response;
return response.json();
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.users)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
dispatch(successGetAllUsers(json));
}
})
.catch(error => {
dispatch(setAlert(error.response, AlertTypes.ERROR));
dispatch(rejectGetAllUsers(error));
});
return null;
};
}
export function successGetAllUsers(json) {
return {
type: actions.GET_ALL_USERSS_SUCCESS,
data: json
};
}
export function rejectGetAllUsers(error) {
return {
type: actions.GET_ALL_USERS_FAILURE,
error: error.toString()
};
}
import * from './authenticationActions.js';
import * from './recipeActions.js';
import * from './fileActions.js';
import * from './adminActions.js';
......@@ -229,9 +229,39 @@ export function deselectRecipe() {
export function resetPlaceholders() {
return {
type: actions.RESET_PLACEHOLDERS
}
};
}
///////////////////////////////////////////////////////////////
/////////////////////// mark chain state //////////////////////
///////////////////////////////////////////////////////////////
//
// export function markAsStarted(recipeId, chainId) {
// console.log("marking chain id as started", chainId)
// return {
// type: actions.MARK_CHAIN_AS_STARTED,
// chainId: chainId,
// recipeId: recipeId
// };
// }
//
// export function markAsCompleted(recipeId, chainId) {
// console.log("marking chain id as completed", chainId)
// return {
// type: actions.MARK_CHAIN_AS_COMPLETED,
// chainId: chainId,
// recipeId: recipeId
// };
// }
//
// export function markAsErrored(recipeId, chainId) {
// return {
// type: actions.MARK_CHAIN_AS_ERRORED,
// chainId: chainId,
// recipeId: recipeId
// };
// }
///////////////////////////////////////////////////////////////
///////////////// new recipe - step class list ////////////////
///////////////////////////////////////////////////////////////
......
......@@ -3,26 +3,68 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';
import * as actions from '../actions/authenticationActions';
import * as actions from '../constants/PusherActions';
import ExecutionDetailStep from './ExecutionDetailStep';
import ExecutionFileList from './ExecutionFileList';
import TimeAgo from 'react-timeago';
import { subscribe, unsubscribe } from 'pusher-redux';
export class ExecutionDetail extends Component {
constructor(props, context) {
super(props, context);
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
}
componentWillMount() {
this.subscribe();
}
componentWillUnmount() {
this.unsubscribe();
}
subscribe() {
// let chainId = this.props.processChain.id;
// let channel = this.processChainChannel(chainId);
let channel = "process_chain_execution";
subscribe(channel, 'processing_started', actions.MARK_CHAIN_AS_STARTED);
subscribe(channel, 'processing_completed', actions.MARK_CHAIN_AS_COMPLETED);
subscribe(channel, 'processing_error', actions.MARK_CHAIN_AS_ERRORED);
subscribe(channel, 'process_step_started', actions.MARK_STEP_AS_STARTED);
subscribe(channel, 'process_step_completed', actions.MARK_STEP_AS_COMPLETED);
}
unsubscribe() {
// let chainId = this.props.processChain.id;
// let channel = this.processChainChannel(chainId);
let channel = "process_chain_execution";
unsubscribe(channel, 'processing_started', actions.MARK_CHAIN_AS_STARTED);
unsubscribe(channel, 'processing_completed', actions.MARK_CHAIN_AS_COMPLETED);
unsubscribe(channel, 'processing_error', actions.MARK_CHAIN_AS_ERRORED);
unsubscribe(channel, 'process_step_started', actions.MARK_STEP_AS_STARTED);
unsubscribe(channel, 'process_step_completed', actions.MARK_STEP_AS_COMPLETED);
}
renderFinishedTitle() {
let { processChain } = this.props;
// const success_or_failure = processChain.finished_at ? "successfully" : "with errors";
return(<span>Finished processing <TimeAgo date={processChain.finished_at} /> </span>);
}
processChainChannel(processChainId) {
return(`process_chain_${processChainId}`);
}
renderInProgressTitle() {
let { processChain } = this.props;
if(_.isNil(processChain.started_at)) {
return(<span>Waiting to be processed (submitted <TimeAgo date={processChain.executed_at} />)</span>);
if(_.isNil(processChain.executed_at)) {
return(<span>Waiting to be processed (submitted <TimeAgo date={processChain.created_at} />) {processChain.executed_at}</span>);
}
return(<span>Started processing (started <TimeAgo date={processChain.started_at} />)</span>);
return(<span>Processing (started <TimeAgo date={processChain.executed_at} />)</span>);
}
renderLinkText(file_name) {
......
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from "lodash";
import * as actions from '../actions/authenticationActions';
......@@ -28,20 +30,23 @@ export class ExecutionDetailStep extends Component {
return("");
}
renderIcon(started_at, execution_errors, successful) {
if(_.isNil(started_at)) {
renderIcon(processStep) {
if(processStep.in_progress === true) {
return(<span className="fa fa-gear fa-spin fa-2x fa-fw step-processing-centered" />);
}
else if(_.isNil(processStep.started_at)) {
return(<span className="small-info-text fa fa-question"/>);
}
else if(successful === true) {
else if(processStep.successful === true) {
return(<span className="fa fa-check"/>);
}
else if(!_.isNil(execution_errors) && !_.isEmpty(execution_errors)) {
return(<span className="fa fa-ban"/>);
else if(!_.isNil(processStep.execution_errors) && !_.isEmpty(processStep.execution_errors)) {
return(<span className="fa fa-times"/>);
}
}
renderLink(filePath, processStepId, finished_at) {
if(!_.isNil(filePath) && !_.isEmpty(filePath) && !_.isUndefined(finished_at)) {
if(!_.isNil(filePath)) {
return(
<a href="#" onClick={e => this.handleOutputFileDownload(e, filePath, processStepId)}>{filePath} <span className="fa fa-download"/></a>
);
......@@ -52,8 +57,8 @@ export class ExecutionDetailStep extends Component {
}
renderZipLink(processStep) {
if(_.isEmpty(processStep.finished_at)) {
return(<span className="small-info-text">no files available <span className="fa fa-download"/></span>);
if(_.isNil(processStep.finished_at)) {
return(<span className="small-info-text">no files available yet <span className="fa fa-download"/></span>);
}
return(
......@@ -73,7 +78,14 @@ export class ExecutionDetailStep extends Component {
}
renderErrors(processStep) {
let errors = processStep.execution_errors || <span className="small-info-text">none</span>;
let errors = processStep.execution_errors;
if(_.isNil(processStep.finished_at)) {
errors = <span/>;
}
else if(_.isNil(errors) || _.isEmpty(errors)) {
errors = <span className="small-info-text">none</span>;
}
return(
<div>Errors: <span className="fail">{ errors }</span></div>
......@@ -81,7 +93,14 @@ export class ExecutionDetailStep extends Component {
}
renderNotes(processStep) {
let notes = processStep.notes || <span className="small-info-text">none</span>;
let notes = processStep.notes;
if(_.isNil(processStep.finished_at)) {
notes = <span/>;
}
else if(_.isNil(notes) || _.isEmpty(notes)) {
notes = <span className="small-info-text">none</span>;
}
return(
<div>Notes: { notes }</div>
......@@ -124,7 +143,12 @@ export class ExecutionDetailStep extends Component {
}
renderStepShorthand(stepName) {
return(<div><span>{this.stepShorthand(stepName)}</span></div>);
let { processStep } = this.props;
let className = "";
if(processStep.in_progress === true) {
className = "bold";
}
return(<div><span className={className}>{this.stepShorthand(stepName)}</span></div>);
}
render() {
......@@ -132,7 +156,7 @@ export class ExecutionDetailStep extends Component {
return(
<tr>
<td className={`cell centered ${this.successClass(processStep.execution_errors, processStep.successful)}`}>{this.renderIcon(processStep.started_at, processStep.execution_errors, processStep.successful)}</td>
<td className={`cell centered ${this.successClass(processStep.execution_errors, processStep.successful)}`}>{this.renderIcon(processStep)}</td>
<td className="cell centered">
{this.renderStepShorthand(processStep.step_class_name)}
{this.renderVersion(processStep.version)}
......@@ -144,8 +168,23 @@ export class ExecutionDetailStep extends Component {
}
ExecutionDetailStep.propTypes = {
dispatch: PropTypes.func,
processStep: PropTypes.object.isRequired,
appState: PropTypes.object
};
export default ExecutionDetailStep;
function mapStateToProps(state) {
return { appState: state.appState };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
dispatch: dispatch
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExecutionDetailStep);
......@@ -38,12 +38,12 @@ export class ExecutionFileList extends Component {
}
else if(isOutput === true) {
return(
<a href="#" onClick={e => this.handleChainOutputFileDownload(e, filePath, id)}>{filePath} <span className="fa fa-download"/></a>
<a href={`#id=${id}`} onClick={e => this.handleChainOutputFileDownload(e, filePath, id)}>{filePath} <span className="fa fa-download"/></a>
);
}
else if(isOutput === false) {
return(
<a href="#" onClick={e => this.handleInputFileDownload(e, filePath, id)}>{filePath} <span className="fa fa-download"/></a>
<a href={`#id=${id}`} onClick={e => this.handleInputFileDownload(e, filePath, id)}>{filePath} <span className="fa fa-download"/></a>
);
}
else {
......@@ -54,12 +54,12 @@ export class ExecutionFileList extends Component {
renderChainZipLink(isOutput, id) {
if(isOutput === true) {
return(
<a href="#" onClick={e => this.handleChainOutputZipDownload(e, id)}>all files (zip) <span className="fa fa-download"/></a>
<a href={`#id=${id}`} onClick={e => this.handleChainOutputZipDownload(e, id)}>all files (zip) <span className="fa fa-download"/></a>
);
}
else if(isOutput === false) {
return(
<a href="#" onClick={e => this.handleInputZipDownload(e, id)}>all files (zip) <span className="fa fa-download"/></a>
<a href={`#id=${id}`} onClick={e => this.handleInputZipDownload(e, id)}>all files (zip) <span className="fa fa-download"/></a>
);
}
else {
......
......@@ -12,8 +12,9 @@ const RecipesList = (props) => {
if(props.getRecipesInProgress === true) {
return(
<div className="recipe-list-container text-center">
<span>Retrieving recipes in progress...</span>
<div className="loading-centered">
<div><span className="fa fa-cog fa-spin fa-3x fa-fw" /></div>
<div><span className="small-info">loading...</span></div>
</div>
);
}
......
......@@ -6,28 +6,43 @@ import SignOutForm from '../components/SignOutForm';
import _ from 'lodash';
import { Link } from 'react-router';
const UserHeader = (props) => {
export class UserHeader extends Component {
let { appState } = props;
let { session } = appState;
renderAdminPanel(user) {
if(user.admin === true) {
return(
<Link to="/admin/dashboard" href="#">Admin panel</Link>
);
}
return;
}
render() {
let { appState } = this.props;
let { session } = appState;
if(_.isNull(session.user)) {
return (
<span><span className="italic">Welcome, guest</span> | <Link to="/signin" href="#">Sign In</Link></span>
);
} else {
return (
<span className="italic">
Signed in as: <a href="#">{session.user.email}</a>
{' '}
<SignOutForm />
</span>
);
if(_.isNull(session.user)) {
return (
<span><span className="italic">Welcome, guest</span> | <Link to="/signin" href="#">Sign In</Link></span>
);
} else {
return (
<span>
{ this.renderAdminPanel(session.user) }
<span className="italic">
Signed in as: <a href="#">{session.user.email}</a>
{' '}
<SignOutForm />
</span>
</span>
);
}
}
};
}
UserHeader.propTypes = {
appState: PropTypes.object.isRequired
appState: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
export default UserHeader;
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router';
import * as actions from '../../actions/adminActions';
import _ from 'lodash';
export class UserListItem extends Component {
render() {
let { user } = this.props;
return(
<Link to={`/admin/users/${user.id}`}>
<li className="user-item">
<div className="user-item__header user-item__unselected">
{user.name}
</div>
<div className="user-item__body">
{user.email}
</div>
</li>
</Link>
);
}
}
UserListItem.propTypes = {
user: PropTypes.object.isRequired,
isSelected: PropTypes.bool.isRequired
};
export default UserListItem;
import React, { PropTypes } from 'react';
import UserListItem from './UserListItem.js';
import _ from 'lodash';
import { Link } from 'react-router';
const UsersList = (props) => {
if(_.isEmpty(props.session)) {
return(
<p className="help-block disabled">Sign in as an admin to see users</p>
);
}
if(props.getUsersInProgress === true) {
return(
<div className="recipe-list-container text-center">
<span>Retrieving users in progress...</span>
</div>
);
}
return (
<div>
<ul className="user-list-container">
{ props.admin.users.map(user =>
<UserListItem key={user.id}
user={user} />)
}
</ul>
</div>
);
};
UsersList.propTypes = {
session: PropTypes.object,
getUsersInProgress: PropTypes.bool.isRequired,
users: PropTypes.array.isRequired
};
export default UsersList;
......@@ -54,3 +54,9 @@ export const DOWNLOAD_FILE_FAILURE = "DOWNLOAD_FILE_FAILURE";
export const AVAILABLE_STEP_LIST_REQUEST = "AVAILABLE_STEP_LIST_REQUEST";
export const AVAILABLE_STEP_LIST_SUCCESS = "AVAILABLE_STEP_LIST_SUCCESS";
export const AVAILABLE_STEP_LIST_FAILURE = "AVAILABLE_STEP_LIST_FAILURE";
/////////////////// admin //////////////////////
export const GET_ALL_USERS_REQUEST = "GET_ALL_USERS_REQUEST";
export const GET_ALL_USERS_SUCCESS = "GET_ALL_USERS_SUCCESS";
export const GET_ALL_USERS_FAILURE = "GET_ALL_USERS_FAILURE";
export const MARK_CHAIN_AS_STARTED = "MARK_CHAIN_AS_STARTED";
export const MARK_CHAIN_AS_COMPLETED = "MARK_CHAIN_AS_COMPLETED";
export const MARK_CHAIN_AS_ERRORED = "MARK_CHAIN_AS_ERRORED";
export const MARK_STEP_AS_STARTED = "MARK_STEP_AS_STARTED";
export const MARK_STEP_AS_COMPLETED = "MARK_STEP_AS_COMPLETED";
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { browserHistory, Link } from 'react-router';
import _ from 'lodash';
import * as actions from '../../actions/adminActions.js';
import Header from '../../components/Header';
import UsersList from '../../components/admin/UsersList';
import AlertList from '../../components/AlertList';
import { setAlert } from '../../actions/actions_helper';
class AdminDashboardPage extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
appState: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
};
componentWillMount = () => {
this.checkAuth();
}
componentDidMount = () => {
const { appState, dispatch } = this.props;
this.checkAuth();
dispatch(actions.getAllUsers(appState));
}
checkAuth() {
const { appState, dispatch } = this.props;
const { session } = appState;
if(_.isNull(session.user)) {
dispatch(setAlert("Please sign in"));
browserHistory.push('/signin');
return;
// } else if(session.user.admin != true) {
// dispatch(setAlert("Authorised users only"));
// browserHistory.push('/');
// return;
}
}
pageContent(appState) {
if(_.isNil(appState.admin.users)) {
return(
<div className="loading-centered">
<div><span className="fa fa-cog fa-spin fa-3x fa-fw" /></div>
<div><span className="small-info">loading...</span></div>
</div>
);
}
else {
return(
<div>
<h1>Admin dashboard</h1>
<h4>accounts & users currently registered</h4>
<UsersList
users={appState.admin.users}
getUsersInProgress={appState.getUsersInProgress}
session={appState.session}
/>
</div>
);
}
}
render() {
const { appState } = this.props;
return (
<div>
<Header
appState={appState}
/>
<div className="content-container">
<div className="breadcrumb-container">
<Link to="/" className="breadcrumb">Home</Link>
<span className="breadcrumb-divider">&gt;&gt;</span>
<Link to="/admin/users" className="breadcrumb"><span className="fa fa-users-o"/> Users</Link>
</div>
<AlertList
alerts={appState.alerts} />
{this.pageContent(appState)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
appState: state.appState,
alerts: PropTypes.array.isRequired,
recipes: PropTypes.array.isRequired
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
dispatch: dispatch
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AdminDashboardPage);
......@@ -6,14 +6,14 @@ import { Link } from 'react-router';
import * as actions from '../../actions/recipeActions';
import _ from 'lodash';
import { getAllRecipes, resetPlaceholders } from '../../actions/recipeActions.js';
import { setAlert } from '../../actions/actions_helper';
import Header from '../../components/Header';
import AlertList from '../../components/AlertList';
import ExecutionFileForm from '../../components/ExecutionFileForm';
import RecipeStepDetail from '../../components/RecipeStepDetail';
import ExecutionList from '../../components/ExecutionList';
import { getAllRecipes, resetPlaceholders } from '../../actions/recipeActions.js';
import { setAlert } from '../../actions/actions_helper';
class RecipeShowPage extends Component {
static propTypes = {
......@@ -151,7 +151,7 @@ class RecipeShowPage extends Component {
</div>
</div>
<div className="recipe-execution-history-container">
<h3>Previous processes ({recipe.times_executed})</h3>
<h3>Previous processes ({recipe.process_chains.length + appState.executionPlaceholderCount})</h3>
{this.renderExecutionList(recipe)}
</div>
</div>
......
import React from 'react';
import {render} from 'react-dom';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import './styles/main.sass';
// import 'font-awesome/css/font-awesome.css';
const store = configureStore();
......
const SLANGER_APP_KEY = "44332211ffeeddccbbaa";
const SLANGER_HTTP_HOST = "localhost";
const SLANGER_HTTP_PORT = 4567;
const SLANGER_WEB_HOST = "localhost";
const SLANGER_WEB_PORT = 4444;
import { configurePusher } from 'pusher-redux';
const options = {
appKey: SLANGER_APP_KEY,
httpHost: SLANGER_HTTP_HOST,
httpPort: SLANGER_HTTP_PORT,
wsHost: SLANGER_WEB_HOST,
wsPort: SLANGER_WEB_PORT
};
export function setupPusher(store) {
configurePusher(store, SLANGER_APP_KEY, options);
}
import * as pusherConstants from '../constants/PusherActions';
import * as actionTypes from '../constants/ActionTypes';
import * as alertTypes from '../constants/AlertTypes.js';
import _ from 'lodash';
......@@ -226,7 +227,7 @@ export d