Setting up structure for Auth flow
parent
2d352b4c1a
commit
fcb252a985
|
@ -0,0 +1,36 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import {withRouter} from 'react-router';
|
||||
|
||||
const CheckAuth = React.createClass({
|
||||
propTypes: {
|
||||
router: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
loggedIn: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
// console.log('checking auth');
|
||||
// this.setState({
|
||||
// loggedIn: true,
|
||||
// });
|
||||
this.props.router.push('/login');
|
||||
},
|
||||
|
||||
render() {
|
||||
const {loggedIn} = this.state;
|
||||
if (!loggedIn) {
|
||||
return <div>AUTH IS BEING CHECKED</div>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
},
|
||||
});
|
||||
|
||||
export default withRouter(CheckAuth);
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import {withRouter} from 'react-router';
|
||||
|
||||
const Login = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<a className="btn btn-primary" href="/oauth">Click me to log in</a>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default withRouter(Login);
|
|
@ -0,0 +1,3 @@
|
|||
import Login from './Login';
|
||||
import CheckAuth from './CheckAuth';
|
||||
export {Login, CheckAuth};
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import React from 'react';
|
||||
import {render} from 'react-dom';
|
||||
import {Provider} from 'react-redux';
|
||||
import {Router, Route, browserHistory} from 'react-router';
|
||||
|
@ -8,18 +8,16 @@ import AlertsApp from 'src/alerts';
|
|||
import CheckSources from 'src/CheckSources';
|
||||
import {HostsPage, HostPage} from 'src/hosts';
|
||||
import {KubernetesPage} from 'src/kubernetes';
|
||||
import {CheckAuth, Login} from 'src/auth';
|
||||
import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor';
|
||||
import DataExplorer from 'src/chronograf';
|
||||
import {CreateSource, SourceForm, ManageSources} from 'src/sources';
|
||||
import NotFound from 'src/shared/components/NotFound';
|
||||
import NoClusterError from 'src/shared/components/NoClusterError';
|
||||
import configureStore from 'src/store/configureStore';
|
||||
import {getSources} from 'shared/apis';
|
||||
|
||||
import 'src/style/enterprise_style/application.scss';
|
||||
|
||||
const {number, shape, string, bool} = PropTypes;
|
||||
|
||||
const defaultTimeRange = {upper: null, lower: 'now() - 15m'};
|
||||
const lsTimeRange = window.localStorage.getItem('timeRange');
|
||||
const parsedTimeRange = JSON.parse(lsTimeRange) || {};
|
||||
|
@ -28,38 +26,7 @@ const timeRange = Object.assign(defaultTimeRange, parsedTimeRange);
|
|||
const store = configureStore({timeRange});
|
||||
const rootNode = document.getElementById('react-root');
|
||||
|
||||
const HTTP_SERVER_ERROR = 500;
|
||||
|
||||
const Root = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
me: {
|
||||
id: 1,
|
||||
name: 'Chronograf',
|
||||
email: 'foo@example.com',
|
||||
admin: true,
|
||||
},
|
||||
isFetching: false,
|
||||
hasReadPermission: false,
|
||||
clusterStatus: null,
|
||||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
me: shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
email: string.isRequired,
|
||||
admin: bool.isRequired,
|
||||
}),
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
me: this.state.me,
|
||||
};
|
||||
},
|
||||
|
||||
activeSource(sources) {
|
||||
const defaultSource = sources.find((s) => s.default);
|
||||
if (defaultSource && defaultSource.id) {
|
||||
|
@ -79,36 +46,31 @@ const Root = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
if (this.state.isFetching) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.clusterStatus === HTTP_SERVER_ERROR) {
|
||||
return <NoClusterError />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={CreateSource} onEnter={this.redirectToHosts} />
|
||||
<Route path="/sources/:sourceID" component={App}>
|
||||
<Route component={CheckSources}>
|
||||
<Route path="manage-sources" component={ManageSources} />
|
||||
<Route path="manage-sources/new" component={SourceForm} />
|
||||
<Route path="manage-sources/:id/edit" component={SourceForm} />
|
||||
<Route path="chronograf/data-explorer" component={DataExplorer} />
|
||||
<Route path="chronograf/data-explorer/:base64ExplorerID" component={DataExplorer} />
|
||||
<Route path="hosts" component={HostsPage} />
|
||||
<Route path="hosts/:hostID" component={HostPage} />
|
||||
<Route path="kubernetes" component={KubernetesPage} />
|
||||
<Route path="kapacitor-config" component={KapacitorPage} />
|
||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||
<Route path="alerts" component={AlertsApp} />
|
||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route component={CheckAuth}>
|
||||
<Route path="/" component={CreateSource} onEnter={this.redirectToHosts} />
|
||||
<Route path="/sources/:sourceID" component={App}>
|
||||
<Route component={CheckSources}>
|
||||
<Route path="manage-sources" component={ManageSources} />
|
||||
<Route path="manage-sources/new" component={SourceForm} />
|
||||
<Route path="manage-sources/:id/edit" component={SourceForm} />
|
||||
<Route path="chronograf/data-explorer" component={DataExplorer} />
|
||||
<Route path="chronograf/data-explorer/:base64ExplorerID" component={DataExplorer} />
|
||||
<Route path="hosts" component={HostsPage} />
|
||||
<Route path="hosts/:hostID" component={HostPage} />
|
||||
<Route path="kubernetes" component={KubernetesPage} />
|
||||
<Route path="kapacitor-config" component={KapacitorPage} />
|
||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||
<Route path="alerts" component={AlertsApp} />
|
||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||
</Route>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Route>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {arrayOf, number, shape, func, string} = PropTypes;
|
||||
|
||||
const AddClusterAccounts = React.createClass({
|
||||
propTypes: {
|
||||
clusters: arrayOf(shape({
|
||||
id: number.isRequired,
|
||||
cluster_users: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
})),
|
||||
dipslay_name: string,
|
||||
cluster_id: string.isRequired,
|
||||
})).isRequired,
|
||||
onSelectClusterAccount: func.isRequired,
|
||||
headerText: string,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
headerText: 'Pair With Cluster Accounts',
|
||||
};
|
||||
},
|
||||
|
||||
handleSelectClusterAccount(e, clusterID) {
|
||||
this.props.onSelectClusterAccount({
|
||||
clusterID,
|
||||
accountName: e.target.value,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.props.clusters.map((cluster, i) => {
|
||||
return (
|
||||
<div key={i} className="form-grid">
|
||||
<div className="form-group col-sm-6">
|
||||
{i === 0 ? <label>Cluster</label> : null}
|
||||
<div className="form-control-static">
|
||||
{cluster.display_name || cluster.cluster_id}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
{i === 0 ? <label>Account</label> : null}
|
||||
{this.renderClusterUsers(cluster)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderClusterUsers(cluster) {
|
||||
if (!cluster.cluster_users) {
|
||||
return (
|
||||
<select disabled={true} defaultValue="No cluster accounts" className="form-control" id="cluster-account">
|
||||
<option>No cluster accounts</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<select onChange={(e) => this.handleSelectClusterAccount(e, cluster.cluster_id)} className="form-control">
|
||||
<option value="">No Association</option>
|
||||
{
|
||||
cluster.cluster_users.map((cu) => {
|
||||
return <option value={cu.name} key={cu.name}>{cu.name}</option>;
|
||||
})
|
||||
}
|
||||
</select>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default AddClusterAccounts;
|
|
@ -1,124 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const CLUSTER_WIDE_PERMISSIONS = ["CreateDatabase", "AddRemoveNode", "ManageShard", "DropDatabase", "CopyShard", "Rebalance"];
|
||||
|
||||
const AddPermissionModal = React.createClass({
|
||||
propTypes: {
|
||||
activeCluster: PropTypes.string.isRequired,
|
||||
permissions: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
})),
|
||||
databases: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
||||
onAddPermission: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
selectedPermission: null,
|
||||
selectedDatabase: '',
|
||||
};
|
||||
},
|
||||
|
||||
handlePermissionClick(permission) {
|
||||
this.setState({
|
||||
selectedPermission: permission,
|
||||
selectedDatabase: '',
|
||||
});
|
||||
},
|
||||
|
||||
handleDatabaseChange(e) {
|
||||
this.setState({selectedDatabase: e.target.value});
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.onAddPermission({
|
||||
name: this.state.selectedPermission,
|
||||
resources: [this.state.selectedDatabase],
|
||||
});
|
||||
$('#addPermissionModal').modal('hide'); // eslint-disable-line no-undef
|
||||
},
|
||||
|
||||
render() {
|
||||
const {permissions} = this.props;
|
||||
|
||||
return (
|
||||
<div className="modal fade" id="addPermissionModal" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Select a Permission to Add</h4>
|
||||
</div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="well permission-list">
|
||||
<ul>
|
||||
{permissions.map((perm) => {
|
||||
return (
|
||||
<li key={perm.name}>
|
||||
<input onClick={() => this.handlePermissionClick(perm.name)} type="radio" name="permissionName" value={`${perm.name}`} id={`permission-${perm.name}`}></input>
|
||||
<label htmlFor={`permission-${perm.name}`}>
|
||||
{perm.displayName}
|
||||
<br/>
|
||||
<span className="permission-description">{perm.description}</span>
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
{this.renderOptions()}
|
||||
</div>
|
||||
{this.renderFooter()}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderFooter() {
|
||||
return (
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<input disabled={!this.state.selectedPermission} className="btn btn-success" type="submit" value="Add Permission"></input>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderOptions() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.selectedPermission ? this.renderDatabases() : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderDatabases() {
|
||||
const isClusterWide = CLUSTER_WIDE_PERMISSIONS.includes(this.state.selectedPermission);
|
||||
if (!this.props.databases.length || isClusterWide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="form-grid">
|
||||
<div className="form-group col-md-12">
|
||||
<label htmlFor="#permissions-database">Limit Permission to...</label>
|
||||
<select onChange={this.handleDatabaseChange} className="form-control" name="database" id="permissions-database">
|
||||
<option value={''}>All Databases</option>
|
||||
{this.props.databases.map((databaseName, i) => <option key={i}>{databaseName}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default AddPermissionModal;
|
|
@ -1,24 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const {node} = React.PropTypes;
|
||||
const ClusterError = React.createClass({
|
||||
propTypes: {
|
||||
children: node.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-sm-6 col-sm-offset-3">
|
||||
<div className="panel panel-error panel-summer">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default ClusterError;
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
import ClusterError from './ClusterError';
|
||||
|
||||
const InsufficientPermissions = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<ClusterError>
|
||||
<div className="panel-heading text-center">
|
||||
<h2 className="deluxe">
|
||||
{`Your account has insufficient permissions`}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="panel-body text-center">
|
||||
<h3 className="deluxe">Talk to your admin to get additional permissions for access</h3>
|
||||
</div>
|
||||
</ClusterError>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default InsufficientPermissions;
|
|
@ -1,35 +0,0 @@
|
|||
import React from 'react';
|
||||
import errorCopy from 'hson!shared/copy/errors.hson';
|
||||
|
||||
const NoClusterError = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-sm-6 col-sm-offset-3">
|
||||
<div className="panel panel-error panel-summer">
|
||||
<div className="panel-heading text-center">
|
||||
<h2 className="deluxe">
|
||||
{errorCopy.noCluster.head}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="panel-body text-center">
|
||||
<h3 className="deluxe">How to resolve:</h3>
|
||||
<p>
|
||||
{errorCopy.noCluster.body}
|
||||
</p>
|
||||
<div className="text-center">
|
||||
<button className="btn btn-center btn-success" onClick={() => window.location.reload()}>My Cluster Is Back Up</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default NoClusterError;
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const NoClusterLinksError = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-sm-6 col-sm-offset-3">
|
||||
<div className="panel panel-error panel-summer">
|
||||
<div className="panel-heading text-center">
|
||||
<h2 className="deluxe">
|
||||
This user is not associated with any cluster accounts!
|
||||
</h2>
|
||||
</div>
|
||||
<div className="panel-body text-center">
|
||||
<p>Many features in Chronograf require your user to be associated with a cluster account.</p>
|
||||
<p>Ask an administrator to associate your user with a cluster account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default NoClusterLinksError;
|
|
@ -1,76 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {arrayOf, shape, string} = PropTypes;
|
||||
|
||||
const PermissionsTable = React.createClass({
|
||||
propTypes: {
|
||||
permissions: PropTypes.arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
description: string.isRequired,
|
||||
resources: arrayOf(string.isRequired).isRequired,
|
||||
})).isRequired,
|
||||
showAddResource: PropTypes.bool,
|
||||
onRemovePermission: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
permissions: [],
|
||||
showAddResource: false,
|
||||
};
|
||||
},
|
||||
|
||||
handleAddResourceClick() {
|
||||
// TODO
|
||||
},
|
||||
|
||||
handleRemovePermission(permission) {
|
||||
this.props.onRemovePermission(permission);
|
||||
},
|
||||
|
||||
render() {
|
||||
if (!this.props.permissions.length) {
|
||||
return (
|
||||
<div className="generic-empty-state">
|
||||
<span className="icon alert-triangle"></span>
|
||||
<h4>This Role has no Permissions</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="panel-body">
|
||||
<table className="table permissions-table">
|
||||
<tbody>
|
||||
{this.props.permissions.map((p) => (
|
||||
<tr key={p.name}>
|
||||
<td>{p.displayName}</td>
|
||||
<td>
|
||||
{p.resources.map((resource, i) => <div key={i} className="pill">{resource === '' ? 'All Databases' : resource}</div>)}
|
||||
{this.props.showAddResource ? (
|
||||
<div onClick={this.handleAddResourceClick} className="pill-add" data-toggle="modal" data-target="#addPermissionModal">
|
||||
<span className="icon plus"></span>
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
{this.props.onRemovePermission ? (
|
||||
<td className="remove-permission">
|
||||
<button
|
||||
onClick={() => this.handleRemovePermission(p)}
|
||||
type="button"
|
||||
className="btn btn-sm btn-link-danger">
|
||||
Remove
|
||||
</button>
|
||||
</td>
|
||||
) : null}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default PermissionsTable;
|
|
@ -1,86 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import {Link} from 'react-router';
|
||||
import PermissionsTable from 'src/shared/components/PermissionsTable';
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes;
|
||||
|
||||
const RolePanels = React.createClass({
|
||||
propTypes: {
|
||||
roles: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
users: arrayOf(string.isRequired).isRequired,
|
||||
permissions: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
displayName: string.isRequired,
|
||||
description: string.isRequired,
|
||||
resources: arrayOf(string.isRequired).isRequired,
|
||||
})).isRequired,
|
||||
})).isRequired,
|
||||
showUserCount: bool,
|
||||
onRemoveAccountFromRole: func,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
showUserCount: false,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const {roles} = this.props;
|
||||
|
||||
if (!roles.length) {
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-body">
|
||||
<div className="generic-empty-state">
|
||||
<span className="icon alert-triangle"></span>
|
||||
<h4>This user has no roles</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="panel-group sub-page" role="tablist">
|
||||
{roles.map((role) => {
|
||||
const id = role.name.replace(/[^\w]/gi, '');
|
||||
return (
|
||||
<div key={role.name} className="panel panel-default">
|
||||
<div className="panel-heading" role="tab" id={`heading${id}`}>
|
||||
<h4 className="panel-title u-flex u-ai-center u-jc-space-between">
|
||||
<a className="collapsed" role="button" data-toggle="collapse" href={`#collapse-role-${id}`}>
|
||||
<span className="caret"></span>
|
||||
{role.name}
|
||||
</a>
|
||||
<div>
|
||||
{this.props.showUserCount ? <p>{role.users ? role.users.length : 0} Users</p> : null}
|
||||
{this.props.onRemoveAccountFromRole ? (
|
||||
<button
|
||||
onClick={() => this.props.onRemoveAccountFromRole(role)}
|
||||
data-toggle="modal"
|
||||
data-target="#removeAccountFromRoleModal"
|
||||
type="button"
|
||||
className="btn btn-sm btn-link">
|
||||
Remove
|
||||
</button>
|
||||
) : null}
|
||||
<Link to={`/roles/${encodeURIComponent(role.name)}`} className="btn btn-xs btn-link">
|
||||
Go To Role
|
||||
</Link>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div id={`collapse-role-${id}`} className="panel-collapse collapse" role="tabpanel">
|
||||
<PermissionsTable permissions={role.permissions} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default RolePanels;
|
|
@ -1,95 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import {Link} from 'react-router';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const {func, shape, arrayOf, string} = PropTypes;
|
||||
const UsersTable = React.createClass({
|
||||
propTypes: {
|
||||
users: arrayOf(shape({}).isRequired).isRequired,
|
||||
activeCluster: string.isRequired,
|
||||
onUserToDelete: func.isRequired,
|
||||
me: shape({}).isRequired,
|
||||
deleteText: string,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
deleteText: 'Delete',
|
||||
};
|
||||
},
|
||||
|
||||
handleSelectUserToDelete(user) {
|
||||
this.props.onUserToDelete(user);
|
||||
},
|
||||
render() {
|
||||
const {users, activeCluster, me} = this.props;
|
||||
|
||||
if (!users.length) {
|
||||
return (
|
||||
<div className="generic-empty-state">
|
||||
<span className="icon user-outline"/>
|
||||
<h4>No users</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="table v-center users-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Admin</th>
|
||||
<th>Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{
|
||||
users.map((user) => {
|
||||
const isMe = me.id === user.id;
|
||||
return (
|
||||
<tr key={user.id}>
|
||||
<td></td>
|
||||
<td>
|
||||
<span>
|
||||
<Link to={`/clusters/${activeCluster}/users/${user.id}`} title={`Go to ${user.name}'s profile`}>{user.name}</Link>
|
||||
{isMe ? <em> (You) </em> : null}
|
||||
</span>
|
||||
</td>
|
||||
<td className="admin-column">{this.renderAdminIcon(user.admin)}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
{this.renderDeleteButton(user)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
|
||||
renderAdminIcon(isAdmin) {
|
||||
return <span className={classNames("icon", {"checkmark text-color-success": isAdmin, "remove text-color-danger": !isAdmin})}></span>;
|
||||
},
|
||||
|
||||
renderDeleteButton(user) {
|
||||
if (this.props.me.id === user.id) {
|
||||
return <button type="button" className="btn btn-sm btn-link-danger disabled" title={`Cannot ${this.props.deleteText} Yourself`}>{this.props.deleteText}</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => this.handleSelectUserToDelete({id: user.id, name: user.name})}
|
||||
type="button"
|
||||
data-toggle="modal"
|
||||
data-target="#deleteUsersModal"
|
||||
className="btn btn-sm btn-link-danger"
|
||||
>
|
||||
{this.props.deleteText}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default UsersTable;
|
Loading…
Reference in New Issue