Commit c41b13bd authored by charlie-ablett's avatar charlie-ablett
Browse files

Add slanger, log downloads, admin panel

parent 0020ba2a
const INK_API_VERSION = 'v1';
var INK_API_VERSION = 'v1';
// module.exports = {
// apiBaseUrl : process.env.NODE_ENV == 'development' ? 'http://localhost:8080' : 'http://ink-api.coko.foundation',
......@@ -16,7 +16,7 @@ function versionHeader() {
function apiUrl() {
var stage = process.env.STAGE;
if(stage == 'development') {
if(process.env.NODE_ENV == 'development') {
return('http://localhost:8080');
} else if(stage == 'production') {
return('http://ink-api.coko.foundation');
......@@ -27,6 +27,6 @@ function apiUrl() {
} else if(stage == 'test') {
return('http://localhost:8080');
} else {
throw new Error("Stage" + stage + "has no matching API address");
throw new Error("Stage " + stage + " has no matching API address");
}
}
......@@ -7,20 +7,20 @@ import _ from 'lodash';
import settings from '../../settings';
export function getAllUsers(appState) {
export function getAllAccounts(appState) {
const { authToken, tokenType, client, expiry, uid } = appState.session;
const signedIn = (authToken != null);
return(getAllUsersWithTokens(signedIn, authToken, tokenType, client, expiry, uid));
return(getAllAccountsWithTokens(signedIn, authToken, tokenType, client, expiry, uid));
}
export function getAllUsersWithTokens(signedIn, authToken, tokenType, client, expiry, uid) {
export function getAllAccountsWithTokens(signedIn, authToken, tokenType, client, expiry, uid) {
return function(dispatch) {
let theResponse;
dispatch({type: actions.GET_ALL_USERS_REQUEST});
fetch(`${settings.apiBaseUrl}/api/admin/users`, {
dispatch({type: actions.GET_ALL_ACCOUNTS_REQUEST});
fetch(`${settings.apiBaseUrl}/api/admin/accounts`, {
method: 'GET',
headers: {
'Accept': settings.apiVersionHeader,
......@@ -38,33 +38,95 @@ export function getAllUsersWithTokens(signedIn, authToken, tokenType, client, ex
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.users)) {
if(_.isUndefined(json.accounts)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
dispatch(successGetAllUsers(json));
dispatch(successGetAllAccounts(json));
}
})
.catch(error => {
dispatch(setAlert(error.response, AlertTypes.ERROR));
dispatch(rejectGetAllUsers(error));
dispatch(rejectGetAllAccounts(error));
});
return null;
};
}
export function successGetAllUsers(json) {
export function successGetAllAccounts(json) {
return {
type: actions.GET_ALL_USERSS_SUCCESS,
data: json
type: actions.GET_ALL_ACCOUNTS_SUCCESS,
accounts: json.accounts
};
}
export function rejectGetAllUsers(error) {
export function rejectGetAllAccounts(error) {
return {
type: actions.GET_ALL_USERS_FAILURE,
type: actions.GET_ALL_ACCOUNTS_FAILURE,
error: error.toString()
};
}
export function getStatusReport(appState) {
const { authToken, tokenType, client, expiry, uid } = appState.session;
const signedIn = (authToken != null);
return(getStatusReportWithTokens(signedIn, authToken, tokenType, client, expiry, uid));
}
export function getStatusReportWithTokens(signedIn, authToken, tokenType, client, expiry, uid) {
return function(dispatch) {
let theResponse;
dispatch({type: actions.GET_STATUS_REPORT_REQUEST});
fetch(`${settings.apiBaseUrl}/check.json`, {
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.results)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
dispatch(successGetStatusReport(json));
}
})
.catch(error => {
dispatch(setAlert(error.response, AlertTypes.ERROR));
dispatch(rejectGetStatusReport(error));
});
return null;
};
}
export function successGetStatusReport(json) {
return {
type: actions.GET_STATUS_REPORT_SUCCESS,
statusReport: json
};
}
export function rejectGetStatusReport(error) {
return {
type: actions.GET_STATUS_REPORT_FAILURE,
error: error.toString()
};
}
......@@ -44,13 +44,13 @@ export function fetchSignIn(email, password, appState) {
}
return(json);
})
.then(response => {
.then(json => {
browserHistory.push('/');
getAllRecipesViaResponse(dispatch, theResponse);
})
.catch(error => {
dispatch(setAlert(error.response));
dispatch(rejectSignIn(error.response));
dispatch(setAlert("Unable to sign you in. Check and try again."));
dispatch(rejectSignIn(dispatch, error.response));
});
return null;
};
......@@ -68,14 +68,13 @@ export function getAllRecipesViaResponse(dispatch, response) {
export function successSignIn(json) {
return {
type: types.SIGN_IN_SUCCESS,
user: json.data
account: json.data.account
};
}
export function rejectSignIn(error) {
export function rejectSignIn(dispatch, error) {
return {
type: types.SIGN_IN_FAILURE,
error: error.toString()
type: types.SIGN_IN_FAILURE
};
}
......
......@@ -31,6 +31,7 @@ export class EditRecipeForm extends Component {
render() {
let { recipe } = this.props;
console.log("recipe to edit:", recipe)
return(
<form ref="editRecipeForm">
......
......@@ -98,6 +98,9 @@ export class ExecutionDetailStep extends Component {
renderFileList(fileList) {
let { processStep } = this.props;
let collapsed = processStep.file_list_collapse;
if(_.isNil(fileList)) {
return (<span className="small-info"><span className="fa fa-ban"/></span>);
}
return(
<SmoothCollapse expanded={!collapsed}>
{fileList.map( file =>
......@@ -122,6 +125,18 @@ export class ExecutionDetailStep extends Component {
);
}
renderProcessLogLink(processStep) {
if(_.isNil(processStep.finished_at)) {
return null;
}
let processLogLocation = processStep.process_log_location;
return(
<div>
<span className="fa fa-hdd-o"/> Log: { this.renderLink(processLogLocation, processStep.id, processStep.finished_at) }
</div>
);
}
renderErrors(processStep) {
let errors = processStep.execution_errors;
......@@ -162,6 +177,7 @@ export class ExecutionDetailStep extends Component {
{ this.renderOutputFiles(processStep) }
{ this.renderErrors(processStep) }
{ this.renderNotes(processStep) }
{ this.renderProcessLogLink(processStep) }
</div>
);
}
......@@ -206,7 +222,9 @@ export class ExecutionDetailStep extends Component {
{this.renderStepShorthand(processStep.step_class_name)}
{this.renderVersion(processStep.version)}
</td>
<td className={this.outputRowClasses(processStep)}>{ this.renderOutput(processStep) }</td>
<td className={this.outputRowClasses(processStep)}>
{ this.renderOutput(processStep) }
</td>
</tr>
);
}
......
......@@ -30,7 +30,6 @@ export class ExecutionList extends Component {
// let chainId = this.props.processChain.id;
// let channel = this.processChainChannel(chainId);
let channel = "process_chain_execution";
console.log(`subscribing to events in ${channel}`);
subscribe(channel, 'processing_started', eventActions.MARK_CHAIN_AS_STARTED);
subscribe(channel, 'processing_completed', eventActions.MARK_CHAIN_AS_COMPLETED);
......@@ -43,7 +42,6 @@ export class ExecutionList extends Component {
// let chainId = this.props.processChain.id;
// let channel = this.processChainChannel(chainId);
let channel = "process_chain_execution";
console.log(`unsubscribing from events in ${channel}`);
unsubscribe(channel, 'processing_started', eventActions.MARK_CHAIN_AS_STARTED);
unsubscribe(channel, 'processing_completed', eventActions.MARK_CHAIN_AS_COMPLETED);
......@@ -65,7 +63,7 @@ export class ExecutionList extends Component {
render() {
let { session, recipe, executionPlaceholderCount } = this.props;
if(_.isNull(session.user)) {
if(_.isNull(session.account)) {
return(
<div className="light-border disabled">
<h4>Finished process chains<span className="fa fa-file"/></h4>
......
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import UserHeader from './UserHeader';
import HeaderAccount from './HeaderAccount';
import HeaderEnvironment from './HeaderEnvironment';
export class Header extends Component {
......@@ -13,7 +13,7 @@ export class Header extends Component {
<Link to="/">
<span className="logo logo__small main-nav__logo" alt="INK logo">INK</span>
</Link>
<UserHeader
<HeaderAccount
appState={this.props.appState}
/>
</div>
......
......@@ -24,7 +24,7 @@ export class SignInForm extends React.Component {
<form onSubmit={this.handleSignIn}>
<div className="form-input-container">
<div className="form-input-labels">
<div className="form-label">user</div>
<div className="form-label">email</div>
<div className="form-label">password</div>
</div>
<div className="form-input-boxes">
......
......@@ -23,7 +23,7 @@ export class StepClassEditableList extends Component {
return (
<li className="stepClassListItem">
<span className="list-item--step-class-name">{stepClassName}</span>
<span className="pull-right">
<span className="pull-right left-pad">
<a href="#" onClick={this.handleRemove}><span className="fa fa-times"/></a>
</span>
</li>
......
......@@ -45,7 +45,7 @@ export class StepClassForm extends Component {
);
}
let options = appState.availableStepClassList.map((name,i) => ({name, value: name, label: name}));
let options = appState.availableStepClassList.map((klass,i) => ({name, value: klass.name, label: klass.name}));
return(
<div className="input--dropdown">
......
......@@ -6,29 +6,40 @@ import * as actions from '../../actions/adminActions';
import _ from 'lodash';
export class UserListItem extends Component {
export class AccountListItem extends Component {
renderAdmin(admin) {
if(admin == true) {
return(<span><span className="fa fa-pie-chart" /> </span>);
} else {
return(null);
}
}
renderAccountDetails(account) {
return(
<span>
<span>{this.renderAdmin(account.admin)}</span>
{account.name} {account.email}
</span>
);
}
render() {
let { user } = this.props;
let { account } = 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>
<div className="account-item">
<Link to={`/admin/accounts/${account.id}`}>
{this.renderAccountDetails(account)}
</Link>
</div>
);
}
}
UserListItem.propTypes = {
user: PropTypes.object.isRequired,
isSelected: PropTypes.bool.isRequired
AccountListItem.propTypes = {
account: PropTypes.object.isRequired
};
export default UserListItem;
export default AccountListItem;
import React, { PropTypes } from 'react';
import UserListItem from './UserListItem.js';
import AccountListItem from './AccountListItem.js';
import _ from 'lodash';
import { Link } from 'react-router';
const UsersList = (props) => {
const AccountsList = (props) => {
if(_.isEmpty(props.session)) {
return(
<p className="help-block disabled">Sign in as an admin to see users</p>
<p className="help-block disabled">Sign in as an admin to see accounts</p>
);
}
if(props.getUsersInProgress === true) {
if(props.getAccountsInProgress === true) {
return(
<div className="recipe-list-container text-center">
<span>Retrieving users in progress...</span>
<span>Retrieving accounts in progress...</span>
</div>
);
}
return (
<div>
<ul className="user-list-container">
{ props.admin.users.map(user =>
<UserListItem key={user.id}
user={user} />)
}
</ul>
<div className="account-list-container">
{ props.accounts.map(account =>
<AccountListItem key={account.id}
account={account} />)
}
</div>
);
};
UsersList.propTypes = {
AccountsList.propTypes = {
session: PropTypes.object,
getUsersInProgress: PropTypes.bool.isRequired,
users: PropTypes.array.isRequired
getAccountsInProgress: PropTypes.bool.isRequired,
accounts: PropTypes.array.isRequired
};
export default UsersList;
export default AccountsList;
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import settings from '../../../settings';
import _ from 'lodash';
import { Link } from 'react-router';
import TimeAgo from 'react-timeago';
export class StatusReport extends Component {
renderReportStatus(status) {
let renderedStatus;
let resultClass;
if(status == "OK") {
renderedStatus = <span><span className="fa fa-check"/> OK</span>;
resultClass = "success";
} else {
renderedStatus = status;
}
return(<td className={`text status-result-row-cell ${resultClass}`}>{renderedStatus}</td>);
}
renderReportName(name) {
if(name == "Sidekiq") {
return(<td className="text status-result-row-cell"><Link to={`${settings.apiBaseUrl}/sidekiq`} target="_blank">Sidekiq <span className="fa fa-external-link"/></Link></td>);
} else {
return(<td className="text status-result-row-cell">{name}</td>);
}
}
renderReportRow(result) {
return(
<tr key={result.name} className="status-result-row">
{this.renderReportName(result.name)}
{this.renderReportStatus(result.status)}
<td className="text status-result-row-cell">{result.message}</td>
</tr>
);
}
render() {
let { statusReport } = this.props;
if(_.isEmpty(this.props.session)) {
return(
<p className="help-block disabled">Sign in to see this</p>
);
}
if(this.props.getStatusReportInProgress == true) {
return(
<div className="recipe-list-container text-center">
<span>getting status report...</span>
</div>
);
} else if(_.isNil(statusReport)) {
return(<div>Could not get status report. What is going on up there?</div>);
} else if(_.isEmpty(statusReport)) {
return(<div>The status report does not look right. Please check the server.</div>);
}
return (
<div>
<div>At {statusReport.timestamp} (<TimeAgo date={statusReport.timestamp} />)</div>
<ul className="status-report-container">
<table>
<thead>
<tr>
<th>Service</th>
<th>Status</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{ statusReport.results.map(result => this.renderReportRow(result)) }
</tbody>
</table>
</ul>
</div>
);
}
}
StatusReport.propTypes = {
session: PropTypes.object,
getStatusReportInProgress: PropTypes.bool.isRequired,
statusReport: PropTypes.object
};
export default StatusReport;
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import HeaderAccount from './HeaderAccount';
import HeaderEnvironment from './HeaderEnvironment';
export class Header extends Component {
render() {
return (
<div>
<HeaderEnvironment />
<div className="main-nav">
<Link to="/">
<span className="logo logo__small main-nav__logo" alt="INK logo">INK</span>
</Link>
<HeaderAccount
appState={this.props.appState}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return { appState: state.appState };
}
Header.propTypes = {
appState: PropTypes.object.isRequired
};
export default connect(
mapStateToProps
)(Header);
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/authenticationActions';
import SignInForm from '../components/SignInForm';
import SignOutForm from '../components/SignOutForm';
import * as actions from '../../actions/authenticationActions';
import SignInForm from '../SignInForm';
import SignOutForm from '../SignOutForm';
import _ from 'lodash';
import { Link } from 'react-router';
export class UserHeader extends Component {
export class HeaderAccount extends Component {
renderAdminPanel(user) {
if(user.admin === true) {
renderAdminPanel(account) {
if(!_.isNil(account) && account.admin === true) {
return(
<Link to="/admin/dashboard" href="#">Admin panel</Link>
<span><Link to="/admin/dashboard" href="#"><span className="fa fa-pie-chart"/> Admin panel</Link> | </span>
);
}
return;
return(null);
}
render() {
let { appState } = this.props;
let { session } = appState;
if(_.isNull(session.user)) {
if(_.isNil(session.account)) {
return (