Merge branch 'master' into feature/influx-write

pull/10616/head
Chris Goller 2017-05-05 08:10:14 -05:00 committed by GitHub
commit 365434783d
35 changed files with 243 additions and 132 deletions

View File

@ -4,13 +4,20 @@
1. [#1364](https://github.com/influxdata/chronograf/pull/1364): Fix link to home when using the --basepath option
1. [#1370](https://github.com/influxdata/chronograf/pull/1370): Remove notification to login outside of session timeout
1. [#1376](https://github.com/influxdata/chronograf/pull/1376): Fix queries built in query builder with math functions in fields
1. [#1399](https://github.com/influxdata/chronograf/pull/1399): User can no longer create a blank template variable by clicking outside a newly added one
### Features
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
### 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. [#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
1. [#1379](https://github.com/influxdata/chronograf/pull/1379): Re-add alert level colors on the alerts page
## v1.2.0-beta10 [2017-04-28]

View File

@ -178,8 +178,7 @@ func Convert(influxQL string) (chronograf.QueryConfig, error) {
// If the condition has a time range we report back its duration
if dur, ok := hasTimeRange(stmt.Condition); ok {
qc.Range = &chronograf.DurationRange{
Lower: shortDur(dur),
Upper: "now",
Lower: "now() - " + shortDur(dur),
}
}

View File

@ -59,8 +59,7 @@ func TestConvert(t *testing.T) {
},
AreTagsAccepted: false,
Range: &chronograf.DurationRange{
Lower: "15m",
Upper: "now",
Lower: "now() - 15m",
},
},
},
@ -96,8 +95,7 @@ func TestConvert(t *testing.T) {
},
AreTagsAccepted: false,
Range: &chronograf.DurationRange{
Lower: "0s",
Upper: "now",
Lower: "now() - 0s",
},
},
},
@ -121,8 +119,7 @@ func TestConvert(t *testing.T) {
},
AreTagsAccepted: false,
Range: &chronograf.DurationRange{
Lower: "15m",
Upper: "now",
Lower: "now() - 15m",
},
},
},
@ -190,8 +187,8 @@ func TestConvert(t *testing.T) {
},
AreTagsAccepted: true,
Range: &chronograf.DurationRange{
Lower: "15m",
Upper: "now",
Lower: "now() - 15m",
Upper: "",
},
},
},
@ -230,8 +227,7 @@ func TestConvert(t *testing.T) {
},
AreTagsAccepted: true,
Range: &chronograf.DurationRange{
Lower: "15m",
Upper: "now",
Lower: "now() - 15m",
},
},
},

View File

@ -284,8 +284,7 @@ func Test_newDashboardResponse(t *testing.T) {
Tags: make(map[string][]string, 0),
AreTagsAccepted: false,
Range: &chronograf.DurationRange{
Lower: "15m",
Upper: "now",
Lower: "now() - 15m",
},
},
},

View File

@ -1,8 +1,6 @@
import reducer from 'src/data_explorer/reducers/timeRange'
import {
setTimeRange,
} from 'src/data_explorer/actions/view'
import {setTimeRange} from 'src/data_explorer/actions/view'
const noopAction = () => {
return {type: 'NOOP'}
@ -12,7 +10,7 @@ describe('DataExplorer.Reducers.TimeRange', () => {
it('it sets the default timeRange', () => {
const state = reducer(undefined, noopAction())
const expected = {
lower: 'now() - 15m',
lower: 'now() - 1h',
upper: null,
}

View File

@ -86,12 +86,13 @@ class CellEditorOverlay extends Component {
handleSaveCell() {
const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state
const {cell, timeRange} = this.props
const {cell} = this.props
const newCell = _.cloneDeep(cell)
newCell.name = cellWorkingName
newCell.type = cellWorkingType
newCell.queries = queriesWorkingDraft.map(q => {
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
const query = q.rawText || buildInfluxQLQuery(timeRange, q)
const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}`

View File

@ -1,14 +1,11 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import omit from 'lodash/omit'
import TemplateControlBar from 'src/dashboards/components/TemplateControlBar'
import LayoutRenderer from 'shared/components/LayoutRenderer'
import Dropdown from 'shared/components/Dropdown'
const Dashboard = ({
source,
timeRange,
dashboard,
onAddCell,
onEditCell,
@ -19,6 +16,7 @@ const Dashboard = ({
onPositionChange,
inPresentationMode,
onOpenTemplateManager,
templatesIncludingDashTime,
onSummonOverlayTechnologies,
onSelectTemplate,
}) => {
@ -26,8 +24,6 @@ const Dashboard = ({
return null
}
const {templates} = dashboard
const cells = dashboard.cells.map(cell => {
const dashboardCell = {...cell}
dashboardCell.queries = dashboardCell.queries.map(
@ -50,43 +46,14 @@ const Dashboard = ({
{'presentation-mode': inPresentationMode}
)}
>
<div className="template-control-bar">
<h1 className="template-control--heading">Template Variables</h1>
{templates.map(({id, values, tempVar}) => {
const items = values.map(value => ({...value, text: value.value}))
const selectedItem = items.find(item => item.selected) || items[0]
const selectedText = selectedItem && selectedItem.text
// TODO: change Dropdown to a MultiSelectDropdown, `selected` to
// the full array, and [item] to all `selected` values when we update
// this component to support multiple values
return (
<div key={id} className="template-control--dropdown">
<Dropdown
items={items}
buttonSize="btn-xs"
selected={selectedText || 'Loading...'}
onChoose={item =>
onSelectTemplate(id, [item].map(x => omit(x, 'text')))}
/>
<label className="template-control--label">
{tempVar}
</label>
</div>
)
})}
<button
className="btn btn-primary btn-sm template-control--manage"
onClick={onOpenTemplateManager}
>
<span className="icon cog-thick" />
Manage
</button>
</div>
<TemplateControlBar
templates={dashboard.templates}
onSelectTemplate={onSelectTemplate}
onOpenTemplateManager={onOpenTemplateManager}
/>
{cells.length
? <LayoutRenderer
timeRange={timeRange}
templates={templates}
templates={templatesIncludingDashTime}
cells={cells}
autoRefresh={autoRefresh}
source={source.links.proxy}
@ -110,7 +77,27 @@ const Dashboard = ({
const {arrayOf, bool, func, shape, string, number} = PropTypes
Dashboard.propTypes = {
dashboard: shape({}).isRequired,
dashboard: shape({
templates: arrayOf(
shape({
type: string.isRequired,
tempVar: string.isRequired,
query: shape({
db: string,
rp: string,
influxql: string,
}),
values: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
selected: bool,
})
).isRequired,
})
).isRequired,
}).isRequired,
templatesIncludingDashTime: arrayOf(shape()).isRequired,
inPresentationMode: bool,
onAddCell: func,
onPositionChange: func,
@ -128,24 +115,6 @@ Dashboard.propTypes = {
timeRange: shape({}).isRequired,
onOpenTemplateManager: func.isRequired,
onSelectTemplate: func.isRequired,
templates: arrayOf(
shape({
type: string.isRequired,
tempVar: string.isRequired,
query: shape({
db: string,
rp: string,
influxql: string,
}),
values: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
selected: bool,
})
).isRequired,
})
),
}
export default Dashboard

View File

@ -0,0 +1,66 @@
import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'
import omit from 'lodash/omit'
const TemplateControlBar = ({
templates,
onSelectTemplate,
onOpenTemplateManager,
}) => (
<div className="template-control-bar">
<div className="template-control--controls">
{templates.map(({id, values, tempVar}) => {
const items = values.map(value => ({...value, text: value.value}))
const selectedItem = items.find(item => item.selected) || items[0]
const selectedText = selectedItem && selectedItem.text
// TODO: change Dropdown to a MultiSelectDropdown, `selected` to
// the full array, and [item] to all `selected` values when we update
// this component to support multiple values
return (
<div key={id} className="template-control--dropdown">
<Dropdown
items={items}
buttonSize="btn-xs"
selected={selectedText || 'Loading...'}
onChoose={item =>
onSelectTemplate(id, [item].map(x => omit(x, 'text')))}
/>
<label className="template-control--label">
{tempVar}
</label>
</div>
)
})}
</div>
<button
className="btn btn-primary btn-sm template-control--manage"
onClick={onOpenTemplateManager}
>
<span className="icon cog-thick" />
Templates
</button>
</div>
)
const {arrayOf, func, shape, string} = PropTypes
TemplateControlBar.propTypes = {
templates: arrayOf(
shape({
id: string.isRequired,
values: arrayOf(
shape({
value: string.isRequired,
})
),
tempVar: string.isRequired,
})
).isRequired,
onSelectTemplate: func.isRequired,
onOpenTemplateManager: func.isRequired,
}
export default TemplateControlBar

View File

@ -301,7 +301,7 @@ class RowWrapper extends Component {
}
handleClickOutside() {
this.setState({isEditing: false})
this.handleCancelEdit()
}
handleStartEdit(name) {

View File

@ -226,6 +226,21 @@ class DashboardPage extends Component {
} = this.props
const dashboard = dashboards.find(d => d.id === +dashboardID)
const dashboardTime = {
id: 'dashtime',
tempVar: ':dashboardTime:',
type: 'constant',
values: [
{
value: timeRange.lower,
type: 'constant',
selected: true,
},
],
}
const templatesIncludingDashTime = (dashboard &&
dashboard.templates.concat(dashboardTime)) || []
const {selectedCell, isEditMode, isTemplating} = this.state
@ -245,7 +260,7 @@ class DashboardPage extends Component {
{selectedCell
? <CellEditorOverlay
source={source}
templates={dashboard.templates}
templates={templatesIncludingDashTime}
cell={selectedCell}
timeRange={timeRange}
autoRefresh={autoRefresh}
@ -302,6 +317,7 @@ class DashboardPage extends Component {
onRenameCell={this.handleRenameDashboardCell}
onUpdateCell={this.handleUpdateDashboardCell}
onOpenTemplateManager={this.handleOpenTemplateManager}
templatesIncludingDashTime={templatesIncludingDashTime}
onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies}
onSelectTemplate={this.handleSelectTemplate}
/>

View File

@ -1,7 +1,7 @@
import _ from 'lodash'
import timeRanges from 'hson!../../shared/data/timeRanges.hson'
const {lower, upper} = timeRanges[1]
const {lower, upper} = timeRanges[2]
const initialState = {
dashboards: [],

View File

@ -97,10 +97,12 @@ export function editRawText(queryId, rawText) {
}
}
export function setTimeRange(range) {
export function setTimeRange(bounds) {
return {
type: 'SET_TIME_RANGE',
payload: range,
payload: {
bounds,
},
}
}

View File

@ -7,7 +7,7 @@ import TagList from './TagList'
import QueryEditor from './QueryEditor'
import buildInfluxQLQuery from 'utils/influxql'
const {arrayOf, func, shape, string} = PropTypes
const {arrayOf, bool, func, shape, string} = PropTypes
const QueryBuilder = React.createClass({
propTypes: {
@ -28,6 +28,7 @@ const QueryBuilder = React.createClass({
tempVar: string.isRequired,
})
),
isInDataExplorer: bool,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
@ -79,7 +80,17 @@ const QueryBuilder = React.createClass({
},
render() {
const {query, timeRange, templates} = this.props
const {query, templates, isInDataExplorer} = this.props
// DE does not understand templating. :dashboardTime: is specific to dashboards
let timeRange
if (isInDataExplorer) {
timeRange = this.props.timeRange
} else {
timeRange = query.range || {upper: null, lower: ':dashboardTime:'}
}
const q = query.rawText || buildInfluxQLQuery(timeRange, query) || ''
return (

View File

@ -66,6 +66,7 @@ class QueryEditor extends Component {
if (isTemplating) {
switch (e.key) {
case 'Tab':
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault()
@ -145,7 +146,8 @@ class QueryEditor extends Component {
const {selectedTemplate} = this.state
const value = this.editor.value
const matches = value.match(TEMPLATE_MATCHER)
if (matches) {
if (matches && !_.isEmpty(templates)) {
// maintain cursor poition
const start = this.editor.selectionStart
const end = this.editor.selectionEnd

View File

@ -4,7 +4,7 @@ import QueryBuilder from './QueryBuilder'
import QueryMakerTab from './QueryMakerTab'
import buildInfluxQLQuery from 'utils/influxql'
const {arrayOf, func, node, number, shape, string} = PropTypes
const {arrayOf, bool, func, node, number, shape, string} = PropTypes
const QueryMaker = React.createClass({
propTypes: {
@ -23,6 +23,7 @@ const QueryMaker = React.createClass({
tempVar: string.isRequired,
})
),
isInDataExplorer: bool,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
@ -74,7 +75,7 @@ const QueryMaker = React.createClass({
},
renderQueryBuilder() {
const {timeRange, actions, source, templates} = this.props
const {timeRange, actions, source, templates, isInDataExplorer} = this.props
const query = this.getActiveQuery()
if (!query) {
@ -101,6 +102,7 @@ const QueryMaker = React.createClass({
query={query}
actions={actions}
onAddQuery={this.handleAddQuery}
isInDataExplorer={isInDataExplorer}
/>
)
},

View File

@ -16,6 +16,7 @@ const VisView = ({
heightPixels,
editQueryStatus,
activeQueryIndex,
isInDataExplorer,
}) => {
const activeQuery = queries[activeQueryIndex]
const defaultQuery = queries[0]
@ -56,7 +57,7 @@ const VisView = ({
autoRefresh={autoRefresh}
templates={templates}
activeQueryIndex={activeQueryIndex}
isInDataExplorer={true}
isInDataExplorer={isInDataExplorer}
showSingleStat={cellType === 'line-plus-single-stat'}
displayOptions={displayOptions}
editQueryStatus={editQueryStatus}
@ -64,7 +65,7 @@ const VisView = ({
)
}
const {arrayOf, func, number, shape, string} = PropTypes
const {arrayOf, bool, func, number, shape, string} = PropTypes
VisView.propTypes = {
view: string.isRequired,
@ -75,6 +76,7 @@ VisView.propTypes = {
heightPixels: number,
editQueryStatus: func.isRequired,
activeQueryIndex: number,
isInDataExplorer: bool,
}
export default VisView

View File

@ -6,7 +6,7 @@ import VisView from 'src/data_explorer/components/VisView'
import {GRAPH, TABLE} from 'src/shared/constants'
import _ from 'lodash'
const {arrayOf, func, number, shape, string} = PropTypes
const {arrayOf, bool, func, number, shape, string} = PropTypes
const META_QUERY_REGEX = /^show/i
const Visualization = React.createClass({
@ -15,6 +15,7 @@ const Visualization = React.createClass({
cellType: string,
autoRefresh: number.isRequired,
templates: arrayOf(shape()),
isInDataExplorer: bool,
timeRange: shape({
upper: string,
lower: string,
@ -86,6 +87,7 @@ const Visualization = React.createClass({
queryConfigs,
editQueryStatus,
activeQueryIndex,
isInDataExplorer,
} = this.props
const {source: {links: {proxy}}} = this.context
const {view} = this.state
@ -121,6 +123,7 @@ const Visualization = React.createClass({
heightPixels={heightPixels}
editQueryStatus={editQueryStatus}
activeQueryIndex={activeQueryIndex}
isInDataExplorer={isInDataExplorer}
/>
</div>
</div>

View File

@ -97,12 +97,14 @@ const DataExplorer = React.createClass({
actions={queryConfigActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
isInDataExplorer={true}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
onDeleteQuery={this.handleDeleteQuery}
activeQueryIndex={activeQueryIndex}
/>
<ResizeBottom>
<Visualization
isInDataExplorer={true}
autoRefresh={autoRefresh}
timeRange={timeRange}
queryConfigs={queryConfigs}

View File

@ -1,23 +1,18 @@
import timeRanges from 'hson!../../shared/data/timeRanges.hson'
const initialLower = timeRanges[1].lower
const initialUpper = timeRanges[1].upper
const {lower, upper} = timeRanges[2]
const initialState = {
upper: initialUpper,
lower: initialLower,
upper,
lower,
}
export default function timeRange(state = initialState, action) {
switch (action.type) {
case 'SET_TIME_RANGE': {
const {upper, lower} = action.payload
const newState = {
upper,
lower,
}
const {bounds} = action.payload
return {...state, ...newState}
return {...state, ...bounds}
}
}
return state

View File

@ -129,7 +129,7 @@ const HostsTable = React.createClass({
onClick={() => this.updateSort('name')}
className={this.sortableClasses('name')}
>
Hostname
Host
</th>
<th
onClick={() => this.updateSort('deltaUptime')}
@ -262,7 +262,7 @@ const SearchBar = React.createClass({
<input
type="text"
className="form-control"
placeholder="Filter by Hostname..."
placeholder="Filter by Host..."
ref="searchInput"
onChange={this.handleChange}
/>

View File

@ -45,12 +45,12 @@ export const HostPage = React.createClass({
},
getInitialState() {
const fifteenMinutesIndex = 1
const timeRange = timeRanges[2]
return {
layouts: [],
hosts: [],
timeRange: timeRanges[fifteenMinutesIndex],
timeRange,
}
},

View File

@ -152,7 +152,7 @@ export const KapacitorRule = React.createClass({
}
if (this.thresholdValueEmpty() || this.relativeValueEmpty()) {
return 'Please enter a value in the Values section'
return 'Please enter a value in the Rule Conditions section'
}
return ''

View File

@ -28,11 +28,11 @@ const KapacitorRules = ({
</PageContents>
)
}
const tableHeader = rules.length === 1 ? '1 Alert Rule' : `${rules.length} Alert Rules`
return (
<PageContents source={source}>
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">Alert Rules</h2>
<h2 className="panel-title">{tableHeader}</h2>
<Link
to={`/sources/${source.id}/alert-rules/new`}
className="btn btn-sm btn-primary"
@ -55,7 +55,7 @@ const PageContents = ({children, source}) => (
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Kapacitor Rules</h1>
<h1 className="page-header__title">Alert Rules</h1>
</div>
<div className="page-header__right">
<SourceIndicator sourceName={source && source.name} />

View File

@ -8,7 +8,7 @@ const KapacitorRulesTable = ({source, rules, onDelete, onChangeRuleStatus}) => {
<thead>
<tr>
<th>Name</th>
<th>Trigger</th>
<th>Rule Type</th>
<th>Message</th>
<th>Alerts</th>
<th className="text-center">Enabled</th>

View File

@ -23,7 +23,7 @@ export const ValuesSection = React.createClass({
return (
<div className="kapacitor-rule-section">
<h3 className="rule-section-heading">Values</h3>
<h3 className="rule-section-heading">Rule Conditions</h3>
<div className="rule-section-body">
<Tabs initialIndex={initialIndex} onSelect={this.handleChooseTrigger}>
<TabList isKapacitorTabs="true">

View File

@ -21,7 +21,6 @@ const AutoRefresh = ComposedComponent => {
templates: arrayOf(
shape({
type: string.isRequired,
label: string.isRequired,
tempVar: string.isRequired,
query: shape({
db: string,

View File

@ -20,7 +20,7 @@ export const LayoutRenderer = React.createClass({
autoRefresh: number.isRequired,
timeRange: shape({
lower: string.isRequired,
}).isRequired,
}),
cells: arrayOf(
shape({
queries: arrayOf(
@ -114,7 +114,6 @@ export const LayoutRenderer = React.createClass({
generateVisualizations() {
const {
timeRange,
source,
cells,
onEditCell,
@ -133,7 +132,8 @@ export const LayoutRenderer = React.createClass({
// on a stable query representation.
let queryText
if (query.queryConfig) {
const {queryConfig: {rawText}} = query
const {queryConfig: {rawText, range}} = query
const timeRange = range || {upper: null, lower: ':dashboardTime:'}
queryText =
rawText || buildInfluxQLQuery(timeRange, query.queryConfig)
} else {

View File

@ -65,7 +65,7 @@ const SideNav = React.createClass({
Alert History
</NavListItem>
<NavListItem link={`${sourcePrefix}/alert-rules`}>
Kapacitor Rules
Alert Rules
</NavListItem>
</NavBlock>
<NavBlock icon="crown2" link={`${sourcePrefix}/admin`}>

View File

@ -22,6 +22,7 @@
@import 'layout/page';
@import 'layout/page-header';
@import 'layout/sidebar';
@import 'layout/overlay';
// Components
@import 'components/confirm-buttons';

View File

@ -6,15 +6,18 @@
*/
$template-control--margin: 2px;
$template-control--min-height: 52px;
.template-control-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
padding: $template-control--margin;
@extend .cell-shell;
background-color: $g0-obsidian;
min-height: $template-control--min-height;
}
.template-control--heading {
font-size: 16px;
@ -25,7 +28,12 @@ $template-control--margin: 2px;
white-space: nowrap;
}
.template-control--manage {
margin: $template-control--margin 8px;
margin: 7px 8px;
}
.template-control--controls {
display: flex;
flex: 1 0 0;
flex-wrap: wrap;
}
.template-control--dropdown {
flex: 0 1 auto;
@ -60,7 +68,7 @@ $template-control--margin: 2px;
&:hover {@include gradient-h($c-comet,$c-pool);}
}
}
}
}
.template-control--label {
@include no-user-select();
order: 1;
@ -73,4 +81,4 @@ $template-control--margin: 2px;
line-height: 18px;
border-radius: $radius-small $radius-small 0 0;
background-color: $g4-onyx;
}
}

View File

@ -0,0 +1,23 @@
/*
Manages Overlays
----------------------------------------------
*/
.page, .sidebar {
position: relative;
}
.page {
z-index: 2;
}
.sidebar {
z-index: 1;
// Ensures that sidebar menus appear above the rest of the app on hover
&:hover {z-index: 2;}
&:hover + .page {z-index: 1;}
}
// Make Overlay Technology full screen
.overlay-technology {
left: -($sidebar-width) !important;
}

View File

@ -13,9 +13,7 @@
color: $g17-whisper;
}
.page {
position: relative;
flex-grow: 1;
overflow: hidden;
}
.page-contents {
position: absolute;
@ -35,15 +33,6 @@
&--purple-scrollbar {
@include custom-scrollbar($g2-kevlar,$c-comet);
}
&.presentation-mode {
top: 0;
height: 100%;
.dashboard {
padding: 12px;
}
}
}
.container-fluid {
padding: ($chronograf-page-header-height / 2) $page-wrapper-padding ($chronograf-page-header-height / 2) $page-wrapper-padding;

View File

@ -41,6 +41,18 @@ $dash-graph-options-arrow: 8px;
}
.dashboard {
&.container-fluid.full-width.page-contents {
padding-top: 8px;
}
&.presentation-mode {
top: 0;
height: 100%;
padding: 12px;
.template-control--manage {
display: none;
}
}
.react-grid-item {
@extend .cell-shell;
}

View File

@ -462,3 +462,13 @@ input.size-49 {width: 49px;}
.dropdown.size-106 .dropdown-toggle {width: 106px;}
.dropdown.size-66 .dropdown-toggle {width: 66px;}
.dropdown.size-49 .dropdown-toggle {width: 49px;}
.alert-level-ok {
&, &:hover {color: $c-rainforest !important;}
}
.alert-level-warning {
&, &:hover {color: $c-pineapple !important;}
}
.alert-level-critical {
&, &:hover {color: $c-dreamsicle !important;}
}

View File

@ -11,7 +11,6 @@ $overlay-z: 100;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: $overlay-z;
padding: 0 30px;