Merge branch 'master' into feature/reverse-kapa

pull/10616/head
Chris Goller 2017-05-05 14:41:06 -05:00 committed by GitHub
commit 2851d607cc
47 changed files with 725 additions and 360 deletions

View File

@ -10,11 +10,13 @@
1. [#1382](https://github.com/influxdata/chronograf/pull/1382): Add line-protocol proxy for InfluxDB data sources
1. [#1391](https://github.com/influxdata/chronograf/pull/1391): :dashboardTime: - Support cell-specific time ranges
1. [#1201](https://github.com/influxdata/chronograf/pull/1201): Allow chronograf to enable/disable all tickscripts.
1. [#1401](https://github.com/influxdata/chronograf/pull/1401): Add support for kapacitor config deletion.
### UI Improvements
1. [#1378](https://github.com/influxdata/chronograf/pull/1378): Save query time range for dashboards
1. [#1365](https://github.com/influxdata/chronograf/pull/1365): Show red indicator on Hosts Page for an offline host
1. [#1373](https://github.com/influxdata/chronograf/pull/1373): Re-address dashboard cell stacking contexts
1. [#1385](https://github.com/influxdata/chronograf/pull/1385): Combined Measurements & Tags columns within the Data Explorer, feels more spacious and intuitive. New design for applying functions to Fields.
1. [#602](https://github.com/influxdata/chronograf/pull/602): Normalize terminology in app
1. [#1392](https://github.com/influxdata/chronograf/pull/1392): Overlays are now full screen
1. [#1395](https://github.com/influxdata/chronograf/pull/1395): Change default global time range to past 1 hour

View File

@ -3,9 +3,7 @@ import React, {Component, PropTypes} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import ResizeContainer, {
ResizeBottom,
} from 'src/shared/components/ResizeContainer'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import QueryMaker from 'src/data_explorer/components/QueryMaker'
import Visualization from 'src/data_explorer/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls'
@ -14,6 +12,7 @@ import * as queryModifiers from 'src/utils/queryTransitions'
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import buildInfluxQLQuery from 'utils/influxql'
import {getQueryConfig} from 'shared/apis'
import {MINIMUM_HEIGHTS} from 'src/data_explorer/constants'
class CellEditorOverlay extends Component {
constructor(props) {
@ -152,8 +151,12 @@ class CellEditorOverlay extends Component {
}
return (
<div className="data-explorer overlay-technology">
<ResizeContainer>
<div className="overlay-technology">
<ResizeContainer
containerClass="resizer--full-size"
minTopHeight={MINIMUM_HEIGHTS.visualization}
minBottomHeight={MINIMUM_HEIGHTS.queryMaker}
>
<Visualization
autoRefresh={autoRefresh}
timeRange={timeRange}
@ -165,29 +168,25 @@ class CellEditorOverlay extends Component {
editQueryStatus={editQueryStatus}
views={[]}
/>
<ResizeBottom>
<div
style={{display: 'flex', flexDirection: 'column', height: '100%'}}
>
<OverlayControls
selectedGraphType={cellWorkingType}
onSelectGraphType={this.handleSelectGraphType}
onCancel={onCancel}
onSave={this.handleSaveCell}
/>
<QueryMaker
source={source}
templates={templates}
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
</div>
</ResizeBottom>
<div className="overlay-technology--editor">
<OverlayControls
selectedGraphType={cellWorkingType}
onSelectGraphType={this.handleSelectGraphType}
onCancel={onCancel}
onSave={this.handleSaveCell}
/>
<QueryMaker
source={source}
templates={templates}
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
</div>
</ResizeContainer>
</div>
)

View File

@ -1,5 +1,5 @@
import React, {Component, PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
import uuid from 'node-uuid'
import TemplateVariableTable
@ -34,7 +34,7 @@ const TemplateVariableManager = ({
Add Variable
</button>
<button
className={classNames('btn btn-success btn-sm', {
className={classnames('btn btn-success btn-sm', {
disabled: !isEdited,
})}
type="button"

View File

@ -3,7 +3,7 @@ import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import OnClickOutside from 'react-onclickoutside'
import classNames from 'classnames'
import classnames from 'classnames'
import Dropdown from 'shared/components/Dropdown'
import DeleteConfirmButtons from 'shared/components/DeleteConfirmButtons'
@ -115,7 +115,7 @@ const TemplateVariableRow = ({
onErrorThrown,
}) => (
<form
className={classNames('template-variable-manager--table-row', {
className={classnames('template-variable-manager--table-row', {
editing: isEditing,
})}
onSubmit={onSubmit({

View File

@ -0,0 +1,77 @@
import React, {PropTypes} from 'react'
import {showDatabases, showRetentionPolicies} from 'shared/apis/metaQuery'
import showDatabasesParser from 'shared/parsing/showDatabases'
import showRetentionPoliciesParser from 'shared/parsing/showRetentionPolicies'
import Dropdown from 'shared/components/Dropdown'
const {func, shape, string} = PropTypes
const DatabaseDropdown = React.createClass({
propTypes: {
query: shape({}).isRequired,
onChooseNamespace: func.isRequired,
},
contextTypes: {
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}).isRequired,
},
getInitialState() {
return {
namespaces: [],
}
},
componentDidMount() {
const {source} = this.context
const proxy = source.links.proxy
showDatabases(proxy).then(resp => {
const {errors, databases} = showDatabasesParser(resp.data)
if (errors.length) {
// do something
}
const namespaces = []
showRetentionPolicies(proxy, databases).then(res => {
res.data.results.forEach((result, index) => {
const {errors: errs, retentionPolicies} = showRetentionPoliciesParser(
result
)
if (errs.length) {
// do something
}
retentionPolicies.forEach(rp => {
namespaces.push({
database: databases[index],
retentionPolicy: rp.name,
})
})
})
this.setState({namespaces})
})
})
},
render() {
const {query, onChooseNamespace} = this.props
const {namespaces} = this.state
return (
<Dropdown
className="dropdown-160 query-builder--db-dropdown"
items={namespaces.map(n => ({...n, text: `${n.database}.${n.retentionPolicy}`}))}
onChoose={onChooseNamespace}
selected={(query.database && query.retentionPolicy) ? `${query.database}.${query.retentionPolicy}` : 'Choose a DB & RP'}
/>
)
},
})
export default DatabaseDropdown

View File

@ -1,21 +1,23 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import classNames from 'classnames'
import {showDatabases, showRetentionPolicies} from 'shared/apis/metaQuery'
import showDatabasesParser from 'shared/parsing/showDatabases'
import showRetentionPoliciesParser from 'shared/parsing/showRetentionPolicies'
const {func, shape, string} = PropTypes
const DatabaseList = React.createClass({
propTypes: {
query: PropTypes.shape({}).isRequired,
onChooseNamespace: PropTypes.func.isRequired,
query: shape({}).isRequired,
onChooseNamespace: func.isRequired,
},
contextTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}).isRequired,
},
@ -59,10 +61,10 @@ const DatabaseList = React.createClass({
},
render() {
const {onChooseNamespace, query} = this.props
const {query, onChooseNamespace} = this.props
return (
<div className="query-builder--column">
<div className="query-builder--column query-builder--column-db">
<div className="query-builder--heading">Databases</div>
<div className="query-builder--list">
{this.state.namespaces.map(namespace => {
@ -73,7 +75,7 @@ const DatabaseList = React.createClass({
return (
<div
className={classNames('query-builder--list-item', {
className={classnames('query-builder--list-item', {
active: isActive,
})}
key={`${database}..${retentionPolicy}`}

View File

@ -1,8 +1,8 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
import _ from 'lodash'
import MultiSelectDropdown from 'src/shared/components/MultiSelectDropdown'
import FunctionSelector from 'src/shared/components/FunctionSelector'
import Dropdown from 'src/shared/components/Dropdown'
import {INFLUXQL_FUNCTIONS} from '../constants'
@ -20,6 +20,19 @@ const FieldListItem = React.createClass({
isKapacitorRule: bool.isRequired,
},
getInitialState() {
return {
isOpen: false,
}
},
toggleFunctionsMenu(e) {
if (e) {
e.stopPropagation()
}
this.setState({isOpen: !this.state.isOpen})
},
handleToggleField() {
this.props.onToggleField(this.props.fieldFunc)
},
@ -29,38 +42,65 @@ const FieldListItem = React.createClass({
field: this.props.fieldFunc.field,
funcs: this.props.isKapacitorRule ? [selectedFuncs.text] : selectedFuncs,
})
this.setState({isOpen: false})
},
render() {
const {isKapacitorRule, fieldFunc, isSelected} = this.props
const {isOpen} = this.state
const {field: fieldText} = fieldFunc
const items = INFLUXQL_FUNCTIONS.map(text => {
return {text}
})
if (isKapacitorRule) {
return (
<div
className={classnames('query-builder--list-item', {active: isSelected})}
key={fieldFunc}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
<span>
<div className="query-builder--checkbox" />
{fieldText}
</span>
{isSelected
? <Dropdown
items={items}
onChoose={this.handleApplyFunctions}
selected={
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
}
/>
: null
}
</div>
)
}
return (
<div
className={classNames('query-builder--list-item', {active: isSelected})}
key={fieldFunc}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
<span>
<div className="query-builder--checkbox" />
{fieldText}
</span>
{isKapacitorRule
? <Dropdown
items={items}
onChoose={this.handleApplyFunctions}
selected={
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
}
/>
: <MultiSelectDropdown
items={INFLUXQL_FUNCTIONS}
<div key={fieldFunc}>
<div
className={classnames('query-builder--list-item', {active: isSelected})}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
<span>
<div className="query-builder--checkbox" />
{fieldText}
</span>
{isSelected
? <div className={classnames('btn btn-xs btn-info', {'function-selector--toggled': isOpen})} onClick={this.toggleFunctionsMenu}>
Functions
</div>
: null
}
</div>
{(isSelected && isOpen)
? <FunctionSelector
onApply={this.handleApplyFunctions}
selectedItems={fieldFunc.funcs || []}
/>}
/>
: null}
</div>
)
},

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
import groupByTimeOptions from 'hson!../data/groupByTimes.hson'
@ -17,7 +17,7 @@ const GroupByTimeDropdown = React.createClass({
return (
<div
className={classNames('dropdown group-by-time-dropdown', {
className={classnames('dropdown group-by-time-dropdown', {
open: isOpen,
})}
>

View File

@ -1,23 +1,29 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
import _ from 'lodash'
import {showMeasurements} from 'shared/apis/metaQuery'
import showMeasurementsParser from 'shared/parsing/showMeasurements'
import TagList from './TagList'
const {func, shape, string} = PropTypes
const MeasurementList = React.createClass({
propTypes: {
query: PropTypes.shape({
database: PropTypes.string,
measurement: PropTypes.string,
query: shape({
database: string,
measurement: string,
}).isRequired,
onChooseMeasurement: PropTypes.func.isRequired,
onChooseMeasurement: func.isRequired,
onChooseTag: func.isRequired,
onToggleTagAcceptance: func.isRequired,
onGroupByTag: func.isRequired,
},
contextTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}).isRequired,
},
@ -69,6 +75,11 @@ const MeasurementList = React.createClass({
})
},
handleAcceptReject(e) {
e.stopPropagation()
this.props.onToggleTagAcceptance()
},
render() {
return (
<div className="query-builder--column">
@ -111,15 +122,34 @@ const MeasurementList = React.createClass({
<div className="query-builder--list">
{measurements.map(measurement => {
const isActive = measurement === this.props.query.measurement
const numTagsActive = Object.keys(this.props.query.tags).length
return (
<div
className={classNames('query-builder--list-item', {
active: isActive,
})}
key={measurement}
onClick={_.wrap(measurement, this.props.onChooseMeasurement)}
>
{measurement}
<div key={measurement}>
<div className={classnames('query-builder--list-item', {active: isActive})}>
<span onClick={isActive ? _.wrap(null, this.props.onChooseMeasurement) : _.wrap(measurement, this.props.onChooseMeasurement)}>
<div className="query-builder--caret icon caret-right"></div>
{measurement}
</span>
{(isActive && numTagsActive >= 1)
? <div className={classnames('flip-toggle', {flipped: this.props.query.areTagsAccepted})} onClick={this.handleAcceptReject}>
<div className="flip-toggle--container">
<div className="flip-toggle--front">!=</div>
<div className="flip-toggle--back">=</div>
</div>
</div>
: null
}
</div>
{
isActive ?
<TagList
query={this.props.query}
onChooseTag={this.props.onChooseTag}
onGroupByTag={this.props.onGroupByTag}
/>
: null
}
</div>
)
})}

View File

@ -1,9 +1,9 @@
import React, {PropTypes} from 'react'
import DatabaseList from './DatabaseList'
import DatabaseDropdown from './DatabaseDropdown'
import MeasurementList from './MeasurementList'
import FieldList from './FieldList'
import TagList from './TagList'
import QueryEditor from './QueryEditor'
import buildInfluxQLQuery from 'utils/influxql'
@ -40,6 +40,7 @@ const QueryBuilder = React.createClass({
toggleTagAcceptance: func.isRequired,
editRawTextAsync: func.isRequired,
}).isRequired,
layout: string,
},
handleChooseNamespace(namespace) {
@ -107,7 +108,37 @@ const QueryBuilder = React.createClass({
},
renderLists() {
const {query} = this.props
const {query, layout} = this.props
// Panel layout uses a dropdown instead of a list for database selection
// Also groups measurements & fields into their own container so they
// can be stacked vertically.
// TODO: Styles to make all this look proper
if (layout === 'panel') {
return (
<div className="query-builder--panel">
<DatabaseDropdown
query={query}
onChooseNamespace={this.handleChooseNamespace}
/>
<div className="query-builder">
<MeasurementList
query={query}
onChooseMeasurement={this.handleChooseMeasurement}
onChooseTag={this.handleChooseTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
onGroupByTag={this.handleGroupByTag}
/>
<FieldList
query={query}
onToggleField={this.handleToggleField}
onGroupByTime={this.handleGroupByTime}
applyFuncsToField={this.handleApplyFuncsToField}
/>
</div>
</div>
)
}
return (
<div className="query-builder">
@ -118,6 +149,9 @@ const QueryBuilder = React.createClass({
<MeasurementList
query={query}
onChooseMeasurement={this.handleChooseMeasurement}
onChooseTag={this.handleChooseTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
onGroupByTag={this.handleGroupByTag}
/>
<FieldList
query={query}
@ -125,12 +159,6 @@ const QueryBuilder = React.createClass({
onGroupByTime={this.handleGroupByTime}
applyFuncsToField={this.handleApplyFuncsToField}
/>
<TagList
query={query}
onChooseTag={this.handleChooseTag}
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
/>
</div>
)
},

View File

@ -1,6 +1,6 @@
import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import classNames from 'classnames'
import classnames from 'classnames'
import Dropdown from 'src/shared/components/Dropdown'
import LoadingDots from 'src/shared/components/LoadingDots'
@ -207,7 +207,7 @@ class QueryEditor extends Component {
spellCheck="false"
/>
<div
className={classNames('varmoji', {'varmoji-rotated': isTemplating})}
className={classnames('varmoji', {'varmoji-rotated': isTemplating})}
>
<div className="varmoji-container">
<div className="varmoji-front">{this.renderStatus(status)}</div>
@ -259,14 +259,14 @@ class QueryEditor extends Component {
return (
<div className="query-editor--status">
<span
className={classNames('query-status-output', {
className={classnames('query-status-output', {
'query-status-output--error': status.error,
'query-status-output--success': status.success,
'query-status-output--warning': status.warn,
})}
>
<span
className={classNames('icon', {
className={classnames('icon', {
stop: status.error,
checkmark: status.success,
'alert-triangle': status.warn,

View File

@ -3,6 +3,7 @@ import React, {PropTypes} from 'react'
import QueryBuilder from './QueryBuilder'
import QueryMakerTab from './QueryMakerTab'
import buildInfluxQLQuery from 'utils/influxql'
import classnames from 'classnames'
const {arrayOf, bool, func, node, number, shape, string} = PropTypes
@ -42,6 +43,7 @@ const QueryMaker = React.createClass({
onDeleteQuery: func.isRequired,
activeQueryIndex: number,
children: node,
layout: string,
},
handleAddQuery() {
@ -65,9 +67,9 @@ const QueryMaker = React.createClass({
},
render() {
const {height, top} = this.props
const {height, top, layout} = this.props
return (
<div className="query-maker" style={{height, top}}>
<div className={classnames('query-maker', {'query-maker--panel': layout === 'panel'})} style={{height, top}}>
{this.renderQueryTabList()}
{this.renderQueryBuilder()}
</div>
@ -75,7 +77,7 @@ const QueryMaker = React.createClass({
},
renderQueryBuilder() {
const {timeRange, actions, source, templates, isInDataExplorer} = this.props
const {timeRange, actions, source, templates, layout, isInDataExplorer} = this.props
const query = this.getActiveQuery()
if (!query) {
@ -94,6 +96,19 @@ const QueryMaker = React.createClass({
)
}
// NOTE
// the layout prop is intended to toggle between a horizontal and vertical layout
// the layout will be horizontal by default
// vertical layout is known as "panel" layout as it will be used to build
// a "cell editor panel" though that term might change
// Currently, if set to "panel" the only noticeable difference is that the
// DatabaseList becomes DatabaseDropdown (more space efficient in vertical layout)
// and is outside the container with measurements/tags/fields
//
// TODO:
// - perhaps switch to something like "isVertical" and accept boolean instead of string
// - more css/markup work to make the alternate appearance look good
return (
<QueryBuilder
source={source}
@ -102,6 +117,7 @@ const QueryMaker = React.createClass({
query={query}
actions={actions}
onAddQuery={this.handleAddQuery}
layout={layout}
isInDataExplorer={isInDataExplorer}
/>
)
@ -147,4 +163,7 @@ const QueryMaker = React.createClass({
},
})
QueryMaker.defaultProps = {
layout: 'default',
}
export default QueryMaker

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
const QueryMakerTab = React.createClass({
propTypes: {
@ -25,7 +25,7 @@ const QueryMakerTab = React.createClass({
render() {
return (
<div
className={classNames('query-maker--tab', {
className={classnames('query-maker--tab', {
active: this.props.isActive,
})}
onClick={this.handleSelect}

View File

@ -3,7 +3,7 @@ import React, {PropTypes} from 'react'
import Dimensions from 'react-dimensions'
import _ from 'lodash'
import moment from 'moment'
import classNames from 'classnames'
import classnames from 'classnames'
import Dropdown from 'shared/components/Dropdown'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
@ -212,7 +212,7 @@ const ChronoTable = React.createClass({
const TabItem = ({name, index, onClickTab, isActive}) => (
<div
className={classNames('table--tab', {active: isActive})}
className={classnames('table--tab', {active: isActive})}
onClick={() => onClickTab(index)}
>
{name}

View File

@ -1,6 +1,5 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import cx from 'classnames'
import TagListItem from './TagListItem'
@ -18,7 +17,6 @@ const TagList = React.createClass({
areTagsAccepted: bool.isRequired,
}).isRequired,
onChooseTag: func.isRequired,
onToggleTagAcceptance: func.isRequired,
onGroupByTag: func.isRequired,
},
@ -97,57 +95,19 @@ const TagList = React.createClass({
this._getTags()
},
handleAcceptReject(e) {
e.stopPropagation()
this.props.onToggleTagAcceptance()
},
render() {
const {query} = this.props
return (
<div className="query-builder--column">
<div className="query-builder--heading">
<span>Tags</span>
{!query.database || !query.measurement || !query.retentionPolicy
? null
: <div
className={cx('flip-toggle', {flipped: query.areTagsAccepted})}
onClick={this.handleAcceptReject}
>
<div className="flip-toggle--container">
<div className="flip-toggle--front">!=</div>
<div className="flip-toggle--back">=</div>
</div>
</div>}
</div>
{this.renderList()}
</div>
)
},
renderList() {
const {database, measurement, retentionPolicy} = this.props.query
if (!database || !measurement || !retentionPolicy) {
return (
<div className="query-builder--list-empty">
<span>No <strong>Measurement</strong> selected</span>
</div>
)
}
return (
<div className="query-builder--list">
<div className="query-builder--sub-list">
{_.map(this.state.tags, (tagValues, tagKey) => {
return (
<TagListItem
key={tagKey}
tagKey={tagKey}
tagValues={tagValues}
selectedTagValues={this.props.query.tags[tagKey] || []}
isUsingGroupBy={
this.props.query.groupBy.tags.indexOf(tagKey) > -1
}
selectedTagValues={query.tags[tagKey] || []}
isUsingGroupBy={query.groupBy.tags.indexOf(tagKey) > -1}
onChooseTag={this.props.onChooseTag}
onGroupByTag={this.props.onGroupByTag}
/>

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import classNames from 'classnames'
import classnames from 'classnames'
const {string, arrayOf, func, bool} = PropTypes
const TagListItem = React.createClass({
@ -69,7 +69,7 @@ const TagListItem = React.createClass({
<span className="icon search" />
</div>
{filtered.map(v => {
const cx = classNames('query-builder--list-item', {
const cx = classnames('query-builder--list-item', {
active: selectedTagValues.indexOf(v) > -1,
})
return (
@ -98,7 +98,7 @@ const TagListItem = React.createClass({
return (
<div>
<div
className={classNames('query-builder--list-item', {active: isOpen})}
className={classnames('query-builder--list-item', {active: isOpen})}
onClick={this.handleClickKey}
>
<span>
@ -106,7 +106,7 @@ const TagListItem = React.createClass({
{tagItemLabel}
</span>
<div
className={classNames('btn btn-info btn-xs group-by-tag', {
className={classnames('btn btn-info btn-xs group-by-tag', {
active: this.props.isUsingGroupBy,
})}
onClick={this.handleGroupBy}

View File

@ -1,23 +1,21 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
const VisHeader = ({views, view, onToggleView, name}) => (
<div className="graph-heading">
<div className="graph-actions">
{views.length
? <ul className="toggle toggle-sm">
{views.map(v => (
<li
key={v}
onClick={() => onToggleView(v)}
className={classNames('toggle-btn ', {active: view === v})}
>
{v}
</li>
))}
</ul>
: null}
</div>
{views.length
? <ul className="toggle toggle-sm">
{views.map(v => (
<li
key={v}
onClick={() => onToggleView(v)}
className={classnames('toggle-btn ', {active: view === v})}
>
{v}
</li>
))}
</ul>
: null}
<div className="graph-title">{name}</div>
</div>
)

View File

@ -24,7 +24,11 @@ const VisView = ({
if (view === 'table') {
if (!query) {
return <div className="generic-empty-state">Enter your query above</div>
return (
<div className="graph-empty">
<p>Build a Query above</p>
</div>
)
}
return (

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import buildInfluxQLQuery from 'utils/influxql'
import classNames from 'classnames'
import classnames from 'classnames'
import VisHeader from 'src/data_explorer/components/VisHeader'
import VisView from 'src/data_explorer/components/VisView'
import {GRAPH, TABLE} from 'src/shared/constants'
@ -109,7 +109,7 @@ const Visualization = React.createClass({
name={cellName}
/>
<div
className={classNames({
className={classnames({
'graph-container': view === GRAPH,
'table-container': view === TABLE,
})}

View File

@ -11,6 +11,11 @@ export const INFLUXQL_FUNCTIONS = [
'stddev',
]
export const MINIMUM_HEIGHTS = {
queryMaker: 350,
visualization: 200,
}
const SEPARATOR = 'SEPARATOR'
export const QUERY_TEMPLATES = [

View File

@ -7,11 +7,10 @@ import _ from 'lodash'
import QueryMaker from '../components/QueryMaker'
import Visualization from '../components/Visualization'
import Header from '../containers/Header'
import ResizeContainer, {
ResizeBottom,
} from 'src/shared/components/ResizeContainer'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import {VIS_VIEWS} from 'src/shared/constants'
import {MINIMUM_HEIGHTS} from '../constants'
import {setAutoRefresh} from 'shared/actions/app'
import * as viewActions from 'src/data_explorer/actions/view'
@ -90,7 +89,11 @@ const DataExplorer = React.createClass({
autoRefresh={autoRefresh}
timeRange={timeRange}
/>
<ResizeContainer>
<ResizeContainer
containerClass="page-contents"
minTopHeight={MINIMUM_HEIGHTS.queryMaker}
minBottomHeight={MINIMUM_HEIGHTS.visualization}
>
<QueryMaker
source={source}
queries={queryConfigs}
@ -102,17 +105,15 @@ const DataExplorer = React.createClass({
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
<ResizeBottom>
<Visualization
isInDataExplorer={true}
autoRefresh={autoRefresh}
timeRange={timeRange}
queryConfigs={queryConfigs}
activeQueryIndex={activeQueryIndex}
editQueryStatus={queryConfigActions.editQueryStatus}
views={VIS_VIEWS}
/>
</ResizeBottom>
<Visualization
isInDataExplorer={true}
autoRefresh={autoRefresh}
timeRange={timeRange}
queryConfigs={queryConfigs}
activeQueryIndex={activeQueryIndex}
editQueryStatus={queryConfigActions.editQueryStatus}
views={VIS_VIEWS}
/>
</ResizeContainer>
</div>
)

View File

@ -4,7 +4,6 @@ import buildInfluxQLQuery from 'utils/influxql'
import DatabaseList from '../../data_explorer/components/DatabaseList'
import MeasurementList from '../../data_explorer/components/MeasurementList'
import FieldList from '../../data_explorer/components/FieldList'
import TagList from '../../data_explorer/components/TagList'
export const DataSection = React.createClass({
propTypes: {
@ -105,6 +104,9 @@ export const DataSection = React.createClass({
<MeasurementList
query={query}
onChooseMeasurement={this.handleChooseMeasurement}
onChooseTag={this.handleChooseTag}
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
/>
<FieldList
query={query}
@ -113,12 +115,6 @@ export const DataSection = React.createClass({
applyFuncsToField={this.handleApplyFuncsToField}
isKapacitorRule={true}
/>
<TagList
query={query}
onChooseTag={this.handleChooseTag}
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
/>
</div>
)
},

View File

@ -3,6 +3,7 @@ import {
getSources,
getKapacitors as getKapacitorsAJAX,
updateKapacitor as updateKapacitorAJAX,
deleteKapacitor as deleteKapacitorAJAX,
} from 'src/shared/apis'
import {publishNotification} from './notifications'
@ -44,6 +45,13 @@ export const setActiveKapacitor = kapacitor => ({
},
})
export const deleteKapacitor = kapacitor => ({
type: 'DELETE_KAPACITOR',
payload: {
kapacitor,
},
})
// Async action creators
export const removeAndLoadSources = source => async dispatch => {
@ -88,3 +96,17 @@ export const setActiveKapacitorAsync = kapacitor => async dispatch => {
const kapacitorPost = {...kapacitor, active: true}
await updateKapacitorAJAX(kapacitorPost)
}
export const deleteKapacitorAsync = kapacitor => async dispatch => {
try {
await deleteKapacitorAJAX(kapacitor)
dispatch(deleteKapacitor(kapacitor))
} catch (err) {
dispatch(
publishNotification(
'error',
'Internal Server Error. Could not delete Kapacitor config.'
)
)
}
}

View File

@ -89,6 +89,18 @@ export const getKapacitors = async source => {
}
}
export const deleteKapacitor = async kapacitor => {
try {
return await AJAX({
method: 'DELETE',
url: kapacitor.links.self,
})
} catch (error) {
console.error(error)
throw error
}
}
export function createKapacitor(
source,
{url, name = 'My Kapacitor', username, password}
@ -151,11 +163,9 @@ export function updateKapacitorConfigSection(kapacitor, section, properties) {
}
export function testAlertOutput(kapacitor, outputName, properties) {
return kapacitorProxy(
kapacitor,
'GET',
'/kapacitor/v1/service-tests'
).then(({data: {services}}) => {
return kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests').then(({
data: {services},
}) => {
const service = services.find(s => s.name === outputName)
return kapacitorProxy(
kapacitor,

View File

@ -1,7 +1,7 @@
import React, {PropTypes, Component} from 'react'
import rome from 'rome'
import moment from 'moment'
import classNames from 'classnames'
import classnames from 'classnames'
import OnClickOutside from 'react-onclickoutside'
class CustomTimeRange extends Component {
@ -46,7 +46,7 @@ class CustomTimeRange extends Component {
return (
<div
className={classNames('custom-time-range', {show: isVisible})}
className={classnames('custom-time-range', {show: isVisible})}
style={{display: 'flex'}}
>
<button

View File

@ -0,0 +1,82 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import {INFLUXQL_FUNCTIONS} from 'src/data_explorer/constants'
class FunctionSelector extends Component {
constructor(props) {
super(props)
this.state = {
localSelectedItems: this.props.selectedItems,
}
this.onSelect = ::this.onSelect
this.handleApplyFunctions = ::this.handleApplyFunctions
}
onSelect(item, e) {
e.stopPropagation()
const {localSelectedItems} = this.state
let nextItems
if (this.isSelected(item)) {
nextItems = localSelectedItems.filter(i => i !== item)
} else {
nextItems = [...localSelectedItems, item]
}
this.setState({localSelectedItems: nextItems})
}
isSelected(item) {
return !!this.state.localSelectedItems.find(text => text === item)
}
handleApplyFunctions(e) {
e.stopPropagation()
this.props.onApply(this.state.localSelectedItems)
}
render() {
const {localSelectedItems} = this.state
return (
<div className="function-selector">
<div className="function-selector--header">
<span>
{localSelectedItems.length > 0
? `${localSelectedItems.length} Selected`
: 'Select functions below'
}
</span>
<div className="btn btn-xs btn-primary" onClick={this.handleApplyFunctions}>Apply</div>
</div>
<div className="function-selector--grid">
{INFLUXQL_FUNCTIONS.map((f, i) => {
return (
<div
key={i}
className={classnames('function-selector--item', {
active: this.isSelected(f),
})}
onClick={_.wrap(f, this.onSelect)}
>{f}</div>
)
})}
</div>
</div>
)
}
}
const {arrayOf, func, string} = PropTypes
FunctionSelector.propTypes = {
onApply: func.isRequired,
selectedItems: arrayOf(string.isRequired).isRequired,
}
export default FunctionSelector

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import Dygraph from 'shared/components/Dygraph'
import classNames from 'classnames'
import classnames from 'classnames'
import shallowCompare from 'react-addons-shallow-compare'
import _ from 'lodash'
@ -91,7 +91,7 @@ export default React.createClass({
if (isFetchingInitially) {
return (
<div className="graph-fetching">
<h3 className="graph-spinner" />
<div className="graph-spinner" />
</div>
)
}
@ -126,7 +126,7 @@ export default React.createClass({
return (
<div
className={classNames('dygraph', {
className={classnames('dygraph', {
'graph--hasYLabel': !!(options.ylabel || options.y2label),
})}
style={{height: '100%'}}

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import classNames from 'classnames'
import classnames from 'classnames'
import _ from 'lodash'
const labelText = ({localSelectedItems, isOpen, label}) => {
@ -48,14 +48,14 @@ class MultiSelectDropdown extends Component {
if (this.isSelected(item)) {
nextItems = localSelectedItems.filter(i => i !== item)
} else {
nextItems = localSelectedItems.concat(item)
nextItems = [...localSelectedItems, item]
}
this.setState({localSelectedItems: nextItems})
}
isSelected(item) {
return this.state.localSelectedItems.indexOf(item) > -1
return !!this.state.localSelectedItems.find(text => text === item)
}
onApplyFunctions(e) {
@ -71,7 +71,7 @@ class MultiSelectDropdown extends Component {
return (
<div
className={classNames('dropdown multi-select-dropdown', {open: isOpen})}
className={classnames('dropdown multi-select-dropdown', {open: isOpen})}
>
<div
onClick={::this.toggleMenu}
@ -108,7 +108,7 @@ class MultiSelectDropdown extends Component {
return (
<li
key={i}
className={classNames('multi-select-dropdown__item', {
className={classnames('multi-select-dropdown__item', {
active: this.isSelected(listItem),
})}
onClick={_.wrap(listItem, this.onSelect)}

View File

@ -1,11 +1,26 @@
import React, {PropTypes} from 'react'
import ResizeHandle from 'shared/components/ResizeHandle'
import classnames from 'classnames'
const {node, string} = PropTypes
const {node, number, string} = PropTypes
const maximumNumChildren = 2
const minimumTopHeight = 200
const minimumBottomHeight = 200
const ResizeContainer = React.createClass({
propTypes: {
children: node.isRequired,
containerClass: string.isRequired,
minTopHeight: number,
minBottomHeight: number,
},
getDefaultProps() {
return {
minTopHeight: minimumTopHeight,
minBottomHeight: minimumBottomHeight,
}
},
getInitialState() {
@ -33,17 +48,18 @@ const ResizeContainer = React.createClass({
return
}
const appHeight = parseInt(
const {minTopHeight, minBottomHeight} = this.props
const oneHundred = 100
const containerHeight = parseInt(
getComputedStyle(this.refs.resizeContainer).height,
10
)
// headingOffset moves the resize handle as many pixels as the page-heading is taking up.
const headingOffset = window.innerHeight - appHeight
const turnToPercent = 100
// verticalOffset moves the resize handle as many pixels as the page-heading is taking up.
const verticalOffset = window.innerHeight - containerHeight
const newTopPanelPercent = Math.ceil(
(e.pageY - headingOffset) / appHeight * turnToPercent
(e.pageY - verticalOffset) / containerHeight * oneHundred
)
const newBottomPanelPercent = turnToPercent - newTopPanelPercent
const newBottomPanelPercent = oneHundred - newTopPanelPercent
// Don't trigger a resize unless the change in size is greater than minResizePercentage
const minResizePercentage = 0.5
@ -54,15 +70,13 @@ const ResizeContainer = React.createClass({
return
}
// Don't trigger a resize if the new sizes are too small
const minTopPanelHeight = 200
const minBottomPanelHeight = 200
const topHeightPixels = newTopPanelPercent / turnToPercent * appHeight
const bottomHeightPixels = newBottomPanelPercent / turnToPercent * appHeight
const topHeightPixels = newTopPanelPercent / oneHundred * containerHeight
const bottomHeightPixels = newBottomPanelPercent / oneHundred * containerHeight
// Don't trigger a resize if the new sizes are too small
if (
topHeightPixels < minTopPanelHeight ||
bottomHeightPixels < minBottomPanelHeight
topHeightPixels < minTopHeight ||
bottomHeightPixels < minBottomHeight
) {
return
}
@ -70,8 +84,6 @@ const ResizeContainer = React.createClass({
this.setState({
topHeight: `${newTopPanelPercent}%`,
bottomHeight: `${newBottomPanelPercent}%`,
topHeightPixels,
bottomHeightPixels,
})
},
@ -87,42 +99,32 @@ const ResizeContainer = React.createClass({
},
render() {
const {topHeight, topHeightPixels, bottomHeightPixels} = this.state
const top = React.cloneElement(this.props.children[0], {
height: topHeight,
heightPixels: topHeightPixels,
})
const bottom = React.cloneElement(this.props.children[1], {
height: `${bottomHeightPixels}px`,
})
const {topHeight, bottomHeight, isDragging} = this.state
const {containerClass, children} = this.props
if (React.Children.count(children) > maximumNumChildren) {
console.error(`There cannot be more than ${maximumNumChildren}' children in ResizeContainer`)
return
}
return (
<div
className="resize-container page-contents"
className={classnames(`resize--container ${containerClass}`, {'resize--dragging': isDragging})}
onMouseLeave={this.handleMouseLeave}
onMouseUp={this.handleStopDrag}
onMouseMove={this.handleDrag}
ref="resizeContainer"
>
{top}
<div className="resize--top" style={{height: topHeight}}>
{React.cloneElement(children[0])}
</div>
{this.renderHandle()}
{bottom}
<div className="resize--bottom" style={{height: bottomHeight, top: topHeight}}>
{React.cloneElement(children[1])}
</div>
</div>
)
},
})
export const ResizeBottom = ({height, children}) => {
const child = React.cloneElement(children, {height})
return (
<div className="resize-bottom" style={{height}}>
{child}
</div>
)
}
ResizeBottom.propTypes = {
children: node.isRequired,
height: string,
}
export default ResizeContainer

View File

@ -1,5 +1,5 @@
import React from 'react'
import cx from 'classnames'
import classnames from 'classnames'
const {func, bool, string} = React.PropTypes
const ResizeHandle = React.createClass({
@ -14,7 +14,7 @@ const ResizeHandle = React.createClass({
return (
<div
className={cx('resizer__handle', {dragging: isDragging})}
className={classnames('resizer--handle', {dragging: isDragging})}
onMouseDown={onHandleStartDrag}
style={{top}}
/>

View File

@ -20,8 +20,8 @@ export default React.createClass({
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
if (this.props.isFetchingInitially) {
return (
<div className="graph-panel__graph-fetching">
<h3 className="graph-panel__spinner" />
<div className="graph-empty">
<h3 className="graph-spinner" />
</div>
)
}

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react'
import cx from 'classnames'
import classnames from 'classnames'
const {node, func, bool, number, string} = PropTypes
export const Tab = React.createClass({
@ -13,7 +13,7 @@ export const Tab = React.createClass({
render() {
return (
<div
className={cx('btn tab', {active: this.props.isActive})}
className={classnames('btn tab', {active: this.props.isActive})}
onClick={this.props.isDisabled ? null : this.props.onClick}
>
{this.props.children}

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import OnClickOutside from 'react-onclickoutside'
import classNames from 'classnames'
import classnames from 'classnames'
const TemplateDrawer = ({
templates,
@ -11,7 +11,7 @@ const TemplateDrawer = ({
<div className="template-drawer">
{templates.map(t => (
<div
className={classNames('template-drawer--item', {
className={classnames('template-drawer--item', {
'template-drawer--selected': t.tempVar === selected.tempVar,
})}
onMouseOver={() => {

View File

@ -54,6 +54,18 @@ const sourcesReducer = (state = initialState, action) => {
})
return updatedSources
}
case 'DELETE_KAPACITOR': {
const {kapacitor} = action.payload
const updatedSources = _.cloneDeep(state)
updatedSources.forEach(source => {
const index = _.findIndex(source.kapacitors, k => k.id === kapacitor.id)
if (index >= 0) {
source.kapacitors.splice(index, 1)
}
})
return updatedSources
}
}
return state

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import {Link} from 'react-router'
import cx from 'classnames'
import classnames from 'classnames'
const {node, string} = PropTypes
@ -16,7 +16,7 @@ const NavListItem = React.createClass({
const isActive = location.startsWith(link)
return (
<Link className={cx('sidebar__menu-item', {active: isActive})} to={link}>
<Link className={classnames('sidebar__menu-item', {active: isActive})} to={link}>
{children}
</Link>
)
@ -63,7 +63,7 @@ const NavBlock = React.createClass({
})
return (
<div className={cx('sidebar__square', className, {active: isActive})}>
<div className={classnames('sidebar__square', className, {active: isActive})}>
{this.renderLink()}
<div className={wrapperClassName || 'sidebar__menu-wrapper'}>
<div className="sidebar__menu">

View File

@ -3,7 +3,13 @@ import {Link, withRouter} from 'react-router'
import Dropdown from 'shared/components/Dropdown'
const kapacitorDropdown = (kapacitors, source, router, setActiveKapacitor) => {
const kapacitorDropdown = (
kapacitors,
source,
router,
setActiveKapacitor,
handleDeleteKapacitor
) => {
if (!kapacitors || kapacitors.length === 0) {
return (
<Link to={`/sources/${source.id}/kapacitors/new`}>Add Kapacitor</Link>
@ -45,6 +51,14 @@ const kapacitorDropdown = (kapacitors, source, router, setActiveKapacitor) => {
router.push(`${item.resource}/edit`)
},
},
{
icon: 'trash',
text: 'delete',
handler: item => {
handleDeleteKapacitor(item.kapacitor)
},
confirmable: true,
},
]}
selected={selected}
/>
@ -58,6 +72,7 @@ const InfluxTable = ({
location,
router,
setActiveKapacitor,
handleDeleteKapacitor,
}) => (
<div className="row">
<div className="col-md-12">
@ -100,7 +115,8 @@ const InfluxTable = ({
s.kapacitors,
s,
router,
setActiveKapacitor
setActiveKapacitor,
handleDeleteKapacitor
)}
</td>
<td className="text-right">
@ -147,6 +163,7 @@ InfluxTable.propTypes = {
}),
sources: array.isRequired,
setActiveKapacitor: func.isRequired,
handleDeleteKapacitor: func.isRequired,
}
export default withRouter(InfluxTable)

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react'
import classNames from 'classnames'
import classnames from 'classnames'
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
import _ from 'lodash'
@ -166,7 +166,7 @@ export const SourceForm = React.createClass({
: null}
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button
className={classNames('btn btn-block', {
className={classnames('btn btn-block', {
'btn-primary': editMode,
'btn-success': !editMode,
})}

View File

@ -6,6 +6,7 @@ import {
removeAndLoadSources,
fetchKapacitorsAsync,
setActiveKapacitorAsync,
deleteKapacitorAsync,
} from 'src/shared/actions/sources'
import InfluxTable from '../components/InfluxTable'
@ -45,7 +46,7 @@ class ManageSources extends Component {
}
render() {
const {sources, source, setActiveKapacitor} = this.props
const {sources, source, setActiveKapacitor, deleteKapacitor} = this.props
return (
<div className="page" id="manage-sources-page">
@ -63,6 +64,7 @@ class ManageSources extends Component {
source={source}
sources={sources}
setActiveKapacitor={setActiveKapacitor}
handleDeleteKapacitor={deleteKapacitor}
/>
</div>
</div>
@ -86,6 +88,7 @@ ManageSources.propTypes = {
removeAndLoadSources: func.isRequired,
fetchKapacitors: func.isRequired,
setActiveKapacitor: func.isRequired,
deleteKapacitor: func.isRequired,
}
const mapStateToProps = ({sources}) => ({
@ -96,6 +99,7 @@ const mapDispatchToProps = dispatch => ({
removeAndLoadSources: bindActionCreators(removeAndLoadSources, dispatch),
fetchKapacitors: bindActionCreators(fetchKapacitorsAsync, dispatch),
setActiveKapacitor: bindActionCreators(setActiveKapacitorAsync, dispatch),
deleteKapacitor: bindActionCreators(deleteKapacitorAsync, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(ManageSources)

View File

@ -45,6 +45,7 @@
@import 'components/search-widget';
@import 'components/source-indicator';
@import 'components/tables';
@import 'components/function-selector';
// Pages

View File

@ -0,0 +1,73 @@
/*
Function Selector
------------------------------------------------------
Used within the FieldListItem component
*/
$function-selector--bg: $g2-kevlar;
$function-selector--gutter: 6px;
$function-selector--size: 26px;
$function-selector--item: $g3-castle;
$function-selector--text: $g11-sidewalk;
$function-selector--item-hover: $g4-onyx;
$function-selector--text-hover: $g15-platinum;
$function-selector--item-active: $c-pool;
$function-selector--text-active: $g20-white;
.function-selector {
padding: 4px 11px 8px 32px;
background-color: $g4-onyx;
}
.function-selector--header {
background-color: $function-selector--bg;
display: flex;
align-items: center;
justify-content: space-between;
padding: $function-selector--gutter;
border-radius: $radius $radius 0 0;
font-size: 12px;
font-weight: 600;
color: $g11-sidewalk;
@include no-user-select();
}
.function-selector--grid {
display: flex;
flex-wrap: wrap;
padding: $function-selector--gutter;
padding-top: 0;
border-radius: 0 0 $radius $radius;
background-color: $function-selector--bg;
}
.function-selector--item {
@include no-user-select();
border-radius: $radius;
font-size: 12px;
font-weight: 700;
font-family: $code-font;
flex: 0 0 calc(25% - 2px);
margin: 1px;
text-align: center;
height: $function-selector--size;
line-height: ($function-selector--size - 3px);
background-color: $function-selector--item;
color: $function-selector--text;
transition:
background-color 0.25s ease,
color 0.25s ease;
&:hover {
background-color: $function-selector--item-hover;
color: $function-selector--text-hover;
cursor: pointer;
}
&.active {
background-color: $function-selector--item-active;
color: $function-selector--text-active;
}
}
.btn.function-selector--toggled {
&, &:hover, &:active, &:hover:active {
background-color: $g7-graphite !important;
color: $g20-white !important;
}
}

View File

@ -3,33 +3,35 @@
---------------------------------------------
*/
$graph-heading-height: 44px;
$graph-gutter: 16px;
/*
Graph Styles
---------------------------------------------
*/
.graph,
.graph-heading,
.graph-container,
.graph > .table-container {
position: relative;
}
.graph {
position: absolute;
top: 0;
width: calc(100% - #{($page-wrapper-padding * 2)});
left: $page-wrapper-padding;
margin: 0 $page-wrapper-padding;
height: 100%;
}
.graph-heading {
position: relative;
top: 16px;
background-color: $g3-castle;
border-radius: $radius $radius 0 0;
display: flex;
align-items: center;
justify-content: space-between;
height: $graph-heading-height;
top: $graph-gutter;
padding: 0 16px;
transition:
background-color 0.25s ease;
.toggle {
margin: 0;
.toggle-btn {
text-transform: capitalize;
}
}
@ -37,22 +39,19 @@ $graph-heading-height: 44px;
font-size: 14px;
color: $g13-mist;
font-weight: 600;
margin-right: 16px;
@include no-user-select();
transition:
color 0.25s ease;
}
.graph-actions {
display: flex;
align-items: center;
}
.graph .table-container {
.graph .table-container,
.graph-container {
top: $graph-gutter;
background-color: $g3-castle;
border-radius: 0 0 $radius $radius;
height: calc(100% - #{($graph-heading-height + ($graph-gutter * 2))});
}
.graph .table-container {
padding: 8px 16px;
position: relative;
top: 16px;
height: calc(100% - #{($graph-heading-height + 32px)});
& > div > div:last-child {
position: absolute;
@ -63,26 +62,10 @@ $graph-heading-height: 44px;
.fixedDataTableLayout_main {
height: 100% !important;
}
.generic-empty-state {
background-color: transparent;
padding: 50px 0;
height: 100%;
font-size: 22px;
@include no-user-select();
}
}
.graph-container {
@include no-user-select();
background-color: $g3-castle;
border-radius: 0 0 $radius $radius;
position: relative;
height: 316px;
transition:
background-color 0.25s ease;
top: 16px;
height: calc(100% - #{$graph-heading-height + 32px});
}
.data-explorer .graph-container {
& > div:not(.graph-panel__refreshing) {
position: absolute;
width: 100%;
@ -101,15 +84,16 @@ $graph-heading-height: 44px;
.graph-empty {
width: 100%;
height: 300px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
align-content: center;
@include no-user-select();
> p {
font-size: 28px;
font-weight: 300;
font-size: 20px;
font-weight: 400;
margin: 0;
text-align: center;
color: $g8-storm;

View File

@ -9,12 +9,13 @@
display: flex;
align-items: stretch;
flex-wrap: nowrap;
overflow-x: auto;
@include custom-scrollbar($query-builder--list-bg,$c-pool);
}
.query-builder--column {
display: flex;
flex-direction: column;
flex: 2 0 0;
}
.query-builder--column-db {
flex: 1 0 0;
}
.query-builder--heading {
@ -200,4 +201,10 @@
.query-builder--list-item:hover .group-by-tag,
.query-builder--list-item.active .group-by-tag {
visibility: visible;
}
.query-builder--db-dropdown {
display: inline-block;
}

View File

@ -11,7 +11,7 @@
border-radius: 0 $radius 0 0;
background-color: $query-editor--bg;
position: relative;
z-index: 2; /* Minimum amount to obcure the toggle flip within Query Builder. Will fix later */
z-index: 3; /* Minimum amount to obcure the toggle flip within Query Builder. Will fix later */
}
.query-editor--field {
font-family: $code-font;

View File

@ -7,9 +7,8 @@
*/
.query-maker {
position: relative;
left: $explorer-page-padding;
width: calc(100% - #{($explorer-page-padding * 2)});
height: 100%;
margin: 0 $explorer-page-padding;
display: flex;
flex-direction: column;
align-items: stretch;

View File

@ -1,3 +1,8 @@
/*
Resizable Container
----------------------------------------------
*/
$resizer-line-width: 2px;
$resizer-line-z: 2;
$resizer-handle-width: 10px;
@ -9,14 +14,40 @@ $resizer-color: $g5-pepper;
$resizer-color-hover: $g8-storm;
$resizer-color-active: $c-pool;
.resizer__handle {
.resize--container {
overflow: hidden !important;
&.resize--dragging * {
@include no-user-select();
}
}
.resize--top,
.resize--bottom {
position: absolute;
width: 100%;
left: 0;
}
.resizer--full-size {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/*
Resizable Container Handle
----------------------------------------------
*/
.resizer--handle {
top: 60%;
left: 0;
height: $resizer-click-area;
margin-top: -$resizer-click-area/2;
margin-bottom: -$resizer-click-area/2;
width: 100%;
z-index: 2;
z-index: 1;
user-select: none;
-webkit-user-select: none;
position: absolute;
@ -78,37 +109,4 @@ $resizer-color-active: $c-pool;
box-shadow: 0 0 $resizer-glow $resizer-color-active;
}
}
}
.resize-container.page-contents {
overflow: hidden;
display: flex;
flex-direction: row;
align-items: stretch;
}
.resize-container {
.resize-top {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
height: 60%;
width: 100%;
}
.resize-bottom {
display: flex;
flex-direction: column;
position: absolute;
height: 40%;
bottom: 0;
left: 0;
width: 100%;
}
}
/* Special rule for when a graph is in the bottom of resizer */
.resize-bottom .graph {
height: 100%;
}
}

View File

@ -15,6 +15,10 @@ $overlay-z: 100;
z-index: $overlay-z;
padding: 0 30px;
/*
Semi-transparent gradient in background
Makes it possible to leave opacity alone
*/
&:before {
content: '';
display: block;
@ -37,7 +41,6 @@ $overlay-z: 100;
flex: 0 0 $overlay-controls-height;
width: calc(100% - #{($explorer-page-padding * 2)});
left: $explorer-page-padding;
margin-top: 16px;
border: 0;
background-color: $g2-kevlar;
border-radius: $radius $radius 0 0;
@ -65,6 +68,13 @@ $overlay-z: 100;
@include no-user-select;
}
}
.overlay-technology--editor {
display: flex;
flex-direction: column;
align-items: stretch;
height: 100%;
padding: 16px 0;
}
.overlay-controls .confirm-buttons {
margin-left: 32px;
}
@ -92,30 +102,12 @@ $overlay-z: 100;
}
/* Graph editing in Dashboards is a little smaller so the dash can be seen in the background */
.overlay-technology .resize-container.page-contents {
background-image: none !important;
overflow: visible;
}
.overlay-technology .graph {
width: 70%;
left: 15%;
}
.overlay-technology .graph-heading,
.overlay-technology .graph-container,
.overlay-technology .table-container {
top: -24px;
}
.overlay-technology .graph-heading .graph-actions {
order: 2;
}
.overlay-technology .graph-container,
.overlay-technology .table-container {
height: calc(100% - 38px);
margin: 0 15%;
}
.overlay-technology .query-maker {
flex: 1 0 0;
padding: 0 8px;
margin-bottom: 8px;
border-radius: 0 0 $radius $radius;
background-color: $g2-kevlar;
}

View File

@ -348,6 +348,7 @@ $toggle-border: 2px;
.toggle {
display: inline-block;
margin: 0;
width: auto;
padding: $toggle-border;
border-radius: 3px;