Merge branch 'feature/934-ew-users' of github.com:influxdata/chronograf into feature/934-ew-users

pull/973/head
Andrew Watkins 2017-03-02 15:04:48 -08:00
commit 532b63eb99
10 changed files with 271 additions and 32 deletions

View File

@ -1,28 +0,0 @@
const express = require('express');
const request = require('request');
const app = express();
app.use('/', (req, res) => {
console.log(`${req.method} ${req.url}`);
const headers = {};
headers['Access-Control-Allow-Origin'] = '*';
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS';
headers['Access-Control-Allow-Credentials'] = false;
headers['Access-Control-Max-Age'] = '86400'; // 24 hours
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept';
res.writeHead(200, headers);
if (req.method === 'OPTIONS') {
res.end();
}
else {
const url = 'http://localhost:8888' + req.url;
req.pipe(request(url)).pipe(res);
}
});
app.listen(3888, () => {
console.log('corsless proxy server now running')
});

View File

@ -17,9 +17,7 @@
"test:lint": "npm run lint; npm run test",
"test:dev": "nodemon --exec npm run test:lint",
"clean": "rm -rf build",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"proxy": "node ./corsless"
"storybook": "node ./storybook"
},
"author": "",
"eslintConfig": {

View File

@ -0,0 +1,137 @@
import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import classNames from 'classnames'
import _ from 'lodash'
const labelText = ({localSelectedItems, isOpen, label}) => {
if (label) {
return label
}
else if (localSelectedItems.length) {
return localSelectedItems.map((s) => s).join(', ')
}
else {
if (isOpen) {
return '0 Selected'
}
else {
return 'Apply Function'
}
}
}
class MultiSelectDropdown extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
localSelectedItems: this.props.selectedItems,
}
this.onSelect = ::this.onSelect
this.onApplyFunctions = ::this.onApplyFunctions
}
componentWillReceiveProps(nextProps) {
if (!_.isEqual(this.state.localSelectedItems, nextProps.selectedItems)) {
this.setState({
localSelectedItems: nextProps.selectedItems,
})
}
}
handleClickOutside() {
this.setState({isOpen: false})
}
toggleMenu(e) {
e.stopPropagation()
this.setState({isOpen: !this.state.isOpen})
}
onSelect(item, e) {
e.stopPropagation()
const {localSelectedItems} = this.state
let nextItems
if (this.isSelected(item)) {
nextItems = localSelectedItems.filter((i) => i !== item)
} else {
nextItems = localSelectedItems.concat(item)
}
this.setState({localSelectedItems: nextItems})
}
isSelected(item) {
return this.state.localSelectedItems.indexOf(item) > -1
}
onApplyFunctions(e) {
e.stopPropagation()
this.setState({isOpen: false})
this.props.onApply(this.state.localSelectedItems)
}
render() {
const {localSelectedItems, isOpen} = this.state
const {label} = this.props
return (
<div className={classNames('dropdown multi-select-dropdown', {open: isOpen})}>
<div onClick={::this.toggleMenu} className="btn btn-xs btn-info dropdown-toggle" type="button">
<div className="multi-select-dropdown__label">
{
labelText({localSelectedItems, isOpen, label})
}
</div>
<span className="caret"></span>
</div>
{this.renderMenu()}
</div>
)
}
renderMenu() {
const {items} = this.props
return (
<div className="dropdown-options">
<li className="multi-select-dropdown__apply" onClick={this.onApplyFunctions} style={{listStyle: 'none'}}>
<div className="btn btn-xs btn-info btn-block">Apply</div>
</li>
<ul className="dropdown-menu multi-select-dropdown__menu" aria-labelledby="dropdownMenu1">
{items.map((listItem, i) => {
return (
<li
key={i}
className={classNames('multi-select-dropdown__item', {active: this.isSelected(listItem)})}
onClick={_.wrap(listItem, this.onSelect)}
>
<a href="#">{listItem}</a>
</li>
)
})}
</ul>
</div>
)
}
}
const {
arrayOf,
func,
string,
} = PropTypes
MultiSelectDropdown.propTypes = {
onApply: func.isRequired,
items: arrayOf(string.isRequired).isRequired,
selectedItems: arrayOf(string.isRequired).isRequired,
label: string,
}
export default OnClickOutside(MultiSelectDropdown)

View File

@ -0,0 +1,21 @@
import React, {PropTypes} from 'react'
import ReactTooltip from 'react-tooltip'
const Tooltip = ({tip, children}) => (
<div>
<div data-tip={tip}>{children}</div>
<ReactTooltip effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip place-bottom" />
</div>
)
const {
shape,
string,
} = PropTypes
Tooltip.propTypes = {
tip: string,
children: shape({}),
}
export default Tooltip

View File

@ -56,3 +56,9 @@
opacity: 1;
}
}
.multi-select-dropdown__label {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding-right: 10px;
}

71
ui/stories/admin.js Normal file
View File

@ -0,0 +1,71 @@
import React from 'react'
import {storiesOf, action, linkTo} from '@kadira/storybook'
import Center from './components/Center'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import Tooltip from 'shared/components/Tooltip'
storiesOf('MultiSelectDropdown', module)
.add('Select Roles w/label', () => (
<Center>
<MultiSelectDropdown
items={[
'Admin',
'User',
'Chrono Giraffe',
'Prophet',
'Susford',
]}
selectedItems={[
'User',
'Chrono Giraffe',
]}
label={'Select Roles'}
onApply={action('onApply')}
/>
</Center>
))
.add('Selected Item list', () => (
<Center>
<MultiSelectDropdown
items={[
'Admin',
'User',
'Chrono Giraffe',
'Prophet',
'Susford',
]}
selectedItems={[
'User',
'Chrono Giraffe',
]}
onApply={action('onApply')}
/>
</Center>
))
.add('0 selected items', () => (
<Center>
<MultiSelectDropdown
items={[
'Admin',
'User',
'Chrono Giraffe',
'Prophet',
'Susford',
]}
selectedItems={[]}
onApply={action('onApply')}
/>
</Center>
))
storiesOf('Tooltip', module)
.add('Delete', () => (
<Center>
<Tooltip tip={`Are you sure? TrashIcon`}>
<div className="btn btn-info btn-sm">
Delete
</div>
</Tooltip>
</Center>
))

View File

@ -0,0 +1,14 @@
import React from 'react'
const Center = ({children}) => (
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%)',
}}>
{children}
</div>
)
export default Center

View File

@ -3,3 +3,4 @@ import 'src/style/chronograf.scss';
// Kapacitor Stories
import './kapacitor'
import './admin'

View File

@ -12,7 +12,7 @@ import queryConfigs from './stubs/queryConfigs';
// Actions for Spies
import * as kapacitorActions from 'src/kapacitor/actions/view'
import * as queryActions from 'src/chronograf/actions/view';
import * as queryActions from 'src/data_explorer/actions/view';
// Components
import KapacitorRule from 'src/kapacitor/components/KapacitorRule';

19
ui/storybook.js Normal file
View File

@ -0,0 +1,19 @@
const express = require('express')
const request = require('request')
const {default: storybook} = require('@kadira/storybook/dist/server/middleware')
const app = express()
const handler = (req, res) => {
console.log(`${req.method} ${req.url}`)
const url = 'http://localhost:8888' + req.url
req.pipe(request(url)).pipe(res)
}
app.use(storybook('./.storybook'))
app.get('/chronograf/v1/*', handler)
app.post('/chronograf/v1/*', handler)
app.listen(6006, () => {
console.log('storybook proxy server now running')
})