Merge remote-tracking branch 'origin/master' into 1481-feature/reset_zoom_on_timerange_change

Conflicts:
	ui/src/shared/components/LayoutRenderer.js
pull/1549/head
Jared Scheib 2017-05-26 19:05:41 -07:00
commit d8af162555
99 changed files with 8127 additions and 5810 deletions

View File

@ -2,6 +2,7 @@
### Bug Fixes
1. [#1530](https://github.com/influxdata/chronograf/pull/1530): Update query config field ordering to always match input query
1. [#1535](https://github.com/influxdata/chronograf/pull/1535): Fix add field functions to existing Kapacitor rules
### Features
@ -9,6 +10,7 @@
1. [#1508](https://github.com/influxdata/chronograf/pull/1508): The enter and escape keys now perform as expected when renaming dashboard headers.
1. [#1524](https://github.com/influxdata/chronograf/pull/1524): Rewrite UI copy in Kapacitor Node configuration to be more clear
1. [#1549](https://github.com/influxdata/chronograf/pull/1549): Reset graph zoom when a new time range is selected
1. [#1544](https://github.com/influxdata/chronograf/pull/1544): Upgrade to new version of Influx Theme, remove excess stylesheets
## v1.3.1.0 [2017-05-22]
@ -35,6 +37,7 @@ In versions 1.3.1+, installing a new version of Chronograf automatically clears
### UI Improvements
1. [#1451](https://github.com/influxdata/chronograf/pull/1451): Refactor scrollbars to support non-webkit browsers
1. [#1453](https://github.com/influxdata/chronograf/pull/1453): Increase the query builder's default height in cell editor mode and in the data explorer
1. [#1453](https://github.com/influxdata/chronograf/pull/1453): Give QueryMaker a greater initial height than Visualization
1. [#1475](https://github.com/influxdata/chronograf/pull/1475): Add ability to toggle visibility of the Template Control Bar
1. [#1464](https://github.com/influxdata/chronograf/pull/1464): Make the [template variables](https://docs.influxdata.com/chronograf/v1.3/guides/dashboard-template-variables/) manager more space efficient

View File

@ -219,7 +219,7 @@
* base64-arraybuffer 0.1.2 [MIT](https://github.com/niklasvh/base64-arraybuffer)
* base64-arraybuffer 0.1.5 [MIT](https://github.com/niklasvh/base64-arraybuffer)
* base64-js 1.2.0 [MIT](http://github.com/beatgammit/base64-js)
* base64id 0.1.0 [Unknown](https://github.com/faeldt/base64id)
* base64id 0.1.0 [MIT](https://github.com/faeldt/base64id/blob/master/LICENSE)
* batch 0.5.3 [MIT](https://github.com/visionmedia/batch)
* bcrypt-pbkdf 1.0.0 [BSD-4-Clause]((none))
* benchmark 1.0.0 [MIT](https://github.com/bestiejs/benchmark.js)

View File

@ -99,7 +99,7 @@ func (c *Client) Create(ctx context.Context, rule chronograf.AlertRule) (*Task,
Href: task.Link.Href,
HrefOutput: c.HrefOutput(kapaID),
TICKScript: script,
Rule: rule,
Rule: c.Reverse(kapaID, script),
}, nil
}
@ -215,6 +215,22 @@ func (c *Client) All(ctx context.Context) (map[string]chronograf.AlertRule, erro
return alerts, nil
}
// Reverse builds a chronograf.AlertRule and its QueryConfig from a tickscript
func (c *Client) Reverse(id string, script chronograf.TICKScript) chronograf.AlertRule {
rule, err := Reverse(script)
if err != nil {
return chronograf.AlertRule{
ID: id,
Name: id,
Query: nil,
TICKScript: script,
}
}
rule.ID = id
rule.TICKScript = script
return rule
}
// Get returns a single alert in kapacitor
func (c *Client) Get(ctx context.Context, id string) (chronograf.AlertRule, error) {
kapa, err := c.kapaClient(c.URL, c.Username, c.Password)
@ -228,18 +244,7 @@ func (c *Client) Get(ctx context.Context, id string) (chronograf.AlertRule, erro
}
script := chronograf.TICKScript(task.TICKscript)
rule, err := Reverse(script)
if err != nil {
return chronograf.AlertRule{
ID: task.ID,
Name: task.ID,
Query: nil,
TICKScript: script,
}, nil
}
rule.ID = task.ID
rule.TICKScript = script
return rule, nil
return c.Reverse(task.ID, script), nil
}
// Update changes the tickscript of a given id.
@ -282,7 +287,7 @@ func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertR
Href: task.Link.Href,
HrefOutput: c.HrefOutput(task.ID),
TICKScript: script,
Rule: rule,
Rule: c.Reverse(task.ID, script),
}, nil
}

View File

@ -15,15 +15,15 @@ type MockKapa struct {
ResTasks []client.Task
Error error
client.CreateTaskOptions
*client.CreateTaskOptions
client.Link
*client.TaskOptions
*client.ListTasksOptions
client.UpdateTaskOptions
*client.UpdateTaskOptions
}
func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) {
m.CreateTaskOptions = opt
m.CreateTaskOptions = &opt
return m.ResTask, m.Error
}
@ -40,7 +40,9 @@ func (m *MockKapa) ListTasks(opt *client.ListTasksOptions) ([]client.Task, error
func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) {
m.Link = link
m.UpdateTaskOptions = opt
if m.UpdateTaskOptions == nil {
m.UpdateTaskOptions = &opt
}
return m.ResTask, m.Error
}
@ -49,6 +51,13 @@ func (m *MockKapa) DeleteTask(link client.Link) error {
return m.Error
}
type MockID struct {
ID string
}
func (m *MockID) Generate() (string, error) {
return m.ID, nil
}
func TestClient_AllStatus(t *testing.T) {
type fields struct {
URL string
@ -160,9 +169,6 @@ func TestClient_AllStatus(t *testing.T) {
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.AllStatus() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) {
t.Errorf("Client.AllStatus() = createTaskOptions %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions)
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.AllStatus() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
@ -438,9 +444,6 @@ trigger
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.All() = %#v, want %#v", got, tt.want)
}
if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) {
t.Errorf("Client.All() = createTaskOptions %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions)
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.All() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
@ -725,9 +728,6 @@ trigger
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.Get() =\n%#v\nwant\n%#v", got, tt.want)
}
if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) {
t.Errorf("Client.Get() = createTaskOptions %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions)
}
if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.Get() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions)
}
@ -743,3 +743,410 @@ trigger
})
}
}
func TestClient_updateStatus(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string) (KapaClient, error)
}
type args struct {
ctx context.Context
href string
status client.TaskStatus
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
updateTaskOptions *client.UpdateTaskOptions
}{
{
name: "disable alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Disabled,
},
resTask: client.Task{
ID: "howdy",
Status: client.Disabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Disabled,
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{},
},
},
{
name: "fail to enable alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Enabled,
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Enabled,
},
resError: fmt.Errorf("error"),
wantErr: true,
},
{
name: "enable alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
status: client.Enabled,
},
resTask: client.Task{
ID: "howdy",
Status: client.Enabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Status: client.Enabled,
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{},
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.Error = tt.resError
kapa.UpdateTaskOptions = nil
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.updateStatus(tt.args.ctx, tt.args.href, tt.args.status)
if (err != nil) != tt.wantErr {
t.Errorf("Client.updateStatus() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.updateStatus() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
t.Errorf("Client.updateStatus() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
}
})
}
}
func TestClient_Update(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string) (KapaClient, error)
}
type args struct {
ctx context.Context
href string
rule chronograf.AlertRule
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
updateTaskOptions *client.UpdateTaskOptions
}{
{
name: "update alert rule error",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resError: fmt.Errorf("error"),
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Type: client.StreamTask,
Status: client.Disabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
wantErr: true,
},
{
name: "update alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
},
args: args{
ctx: context.Background(),
href: "/kapacitor/v1/tasks/howdy",
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resTask: client.Task{
ID: "howdy",
Status: client.Enabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/howdy",
},
},
updateTaskOptions: &client.UpdateTaskOptions{
TICKscript: "",
Type: client.StreamTask,
Status: client.Disabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "howdy",
Href: "/kapacitor/v1/tasks/howdy",
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
Rule: chronograf.AlertRule{
ID: "howdy",
Name: "howdy",
},
},
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.Error = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.Update(tt.args.ctx, tt.args.href, tt.args.rule)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Update() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.Update() =\n%#+v\n, want\n%#+v\n", got, tt.want)
}
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
t.Errorf("Client.Update() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
}
})
}
}
func TestClient_Create(t *testing.T) {
type fields struct {
URL string
Username string
Password string
ID chronograf.ID
Ticker chronograf.Ticker
kapaClient func(url, username, password string) (KapaClient, error)
}
type args struct {
ctx context.Context
rule chronograf.AlertRule
}
kapa := &MockKapa{}
tests := []struct {
name string
fields fields
args args
resTask client.Task
want *Task
resError error
wantErr bool
createTaskOptions *client.CreateTaskOptions
}{
{
name: "create alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
ID: &MockID{
ID: "howdy",
},
},
args: args{
ctx: context.Background(),
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resTask: client.Task{
ID: "chronograf-v1-howdy",
Status: client.Enabled,
Link: client.Link{
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
},
},
createTaskOptions: &client.CreateTaskOptions{
TICKscript: "",
ID: "chronograf-v1-howdy",
Type: client.StreamTask,
Status: client.Enabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
want: &Task{
ID: "chronograf-v1-howdy",
Href: "/kapacitor/v1/tasks/chronograf-v1-howdy",
HrefOutput: "/kapacitor/v1/tasks/chronograf-v1-howdy/output",
Rule: chronograf.AlertRule{
ID: "chronograf-v1-howdy",
Name: "chronograf-v1-howdy",
},
},
},
{
name: "create alert rule",
fields: fields{
kapaClient: func(url, username, password string) (KapaClient, error) {
return kapa, nil
},
Ticker: &Alert{},
ID: &MockID{
ID: "howdy",
},
},
args: args{
ctx: context.Background(),
rule: chronograf.AlertRule{
ID: "howdy",
Query: &chronograf.QueryConfig{
Database: "db",
RetentionPolicy: "rp",
},
},
},
resError: fmt.Errorf("error"),
createTaskOptions: &client.CreateTaskOptions{
TICKscript: "",
ID: "chronograf-v1-howdy",
Type: client.StreamTask,
Status: client.Enabled,
DBRPs: []client.DBRP{
{
Database: "db",
RetentionPolicy: "rp",
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
kapa.ResTask = tt.resTask
kapa.Error = tt.resError
t.Run(tt.name, func(t *testing.T) {
c := &Client{
URL: tt.fields.URL,
Username: tt.fields.Username,
Password: tt.fields.Password,
ID: tt.fields.ID,
Ticker: tt.fields.Ticker,
kapaClient: tt.fields.kapaClient,
}
got, err := c.Create(tt.args.ctx, tt.args.rule)
if (err != nil) != tt.wantErr {
t.Errorf("Client.Create() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.Create() =\n%v\n, want\n%v\n", got, tt.want)
}
if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) {
t.Errorf("Client.Create() = %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions)
}
})
}
}

View File

@ -398,6 +398,26 @@ func newAlertResponse(rule chronograf.AlertRule, tickScript chronograf.TICKScrip
return res
}
// ValidRuleRequest checks if the requested rule change is valid
func ValidRuleRequest(rule chronograf.AlertRule) error {
if rule.Query == nil {
return fmt.Errorf("invalid alert rule: no query defined")
}
var hasFuncs bool
for _, f := range rule.Query.Fields {
if len(f.Funcs) > 0 {
hasFuncs = true
break
}
}
// All kapacitor rules with functions must have a window that is applied
// every amount of time
if rule.Every == "" && hasFuncs {
return fmt.Errorf(`invalid alert rule: functions require an "every" window`)
}
return nil
}
// KapacitorRulesPut proxies PATCH to kapacitor
func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) {
id, err := paramID("kid", r)
@ -451,8 +471,7 @@ func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) {
Error(w, http.StatusInternalServerError, err.Error(), h.Logger)
return
}
res := newAlertResponse(req, task.TICKScript, task.Href, task.HrefOutput, "enabled", srv.SrcID, srv.ID)
res := newAlertResponse(task.Rule, task.TICKScript, task.Href, task.HrefOutput, "enabled", srv.SrcID, srv.ID)
encodeJSON(w, http.StatusOK, res, h.Logger)
}

56
server/kapacitors_test.go Normal file
View File

@ -0,0 +1,56 @@
package server
import (
"testing"
"github.com/influxdata/chronograf"
)
func TestValidRuleRequest(t *testing.T) {
tests := []struct {
name string
rule chronograf.AlertRule
wantErr bool
}{
{
name: "No every with functions",
rule: chronograf.AlertRule{
Query: &chronograf.QueryConfig{
Fields: []chronograf.Field{
{
Field: "oldmanpeabody",
Funcs: []string{"max"},
},
},
},
},
wantErr: true,
},
{
name: "With every",
rule: chronograf.AlertRule{
Every: "10s",
Query: &chronograf.QueryConfig{
Fields: []chronograf.Field{
{
Field: "oldmanpeabody",
Funcs: []string{"max"},
},
},
},
},
},
{
name: "No query config",
rule: chronograf.AlertRule{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ValidRuleRequest(tt.rule); (err != nil) != tt.wantErr {
t.Errorf("ValidRuleRequest() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -4,6 +4,8 @@ import {ALERT_NODES_ACCESSORS} from 'src/kapacitor/constants'
import {
chooseTrigger,
addEvery,
removeEvery,
updateRuleValues,
updateDetails,
updateMessage,
@ -38,6 +40,23 @@ describe('Kapacitor.Reducers.rules', () => {
expect(newState[ruleID].values).to.equal(defaultRuleConfigs.threshold)
})
it('can update the every', () => {
const ruleID = 1
const initialState = {
[ruleID]: {
id: ruleID,
queryID: 988,
every: null,
},
}
let newState = reducer(initialState, addEvery(ruleID, '30s'))
expect(newState[ruleID].every).to.equal('30s')
newState = reducer(newState, removeEvery(ruleID))
expect(newState[ruleID].every).to.equal(null)
})
it('can update the values', () => {
const ruleID = 1
const initialState = {
@ -50,11 +69,17 @@ describe('Kapacitor.Reducers.rules', () => {
}
const newDeadmanValues = {duration: '5m'}
const newState = reducer(initialState, updateRuleValues(ruleID, 'deadman', newDeadmanValues))
const newState = reducer(
initialState,
updateRuleValues(ruleID, 'deadman', newDeadmanValues)
)
expect(newState[ruleID].values).to.equal(newDeadmanValues)
const newRelativeValues = {func: 'max', change: 'change'}
const finalState = reducer(newState, updateRuleValues(ruleID, 'relative', newRelativeValues))
const finalState = reducer(
newState,
updateRuleValues(ruleID, 'relative', newRelativeValues)
)
expect(finalState[ruleID].trigger).to.equal('relative')
expect(finalState[ruleID].values).to.equal(newRelativeValues)
})
@ -110,7 +135,10 @@ describe('Kapacitor.Reducers.rules', () => {
.services('a b c')
`
let newState = reducer(initialState, updateAlertNodes(ruleID, 'alerta', tickScript))
let newState = reducer(
initialState,
updateAlertNodes(ruleID, 'alerta', tickScript)
)
const expectedStr = `alerta().resource('Hostname or service').event('Something went wrong').environment('Development').group('Dev. Servers').services('a b c')`
let actualStr = ALERT_NODES_ACCESSORS.alerta(newState[ruleID])
@ -184,7 +212,10 @@ describe('Kapacitor.Reducers.rules', () => {
},
}
const newState = reducer(initialState, updateRuleStatusSuccess(ruleID, status))
const newState = reducer(
initialState,
updateRuleStatusSuccess(ruleID, status)
)
expect(newState[ruleID].status).to.equal(status)
})
})

View File

@ -96,7 +96,7 @@ const AdminTabs = ({
<TabList customClass="col-md-2 admin-tabs">
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
</TabList>
<TabPanels customClass="col-md-10">
<TabPanels customClass="col-md-10 admin-tabs--content">
{tabs.map((t, i) => (
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
))}

View File

@ -48,17 +48,17 @@ class ChangePassRow extends Component {
}
render() {
const {user} = this.props
const {user, buttonSize} = this.props
if (this.state.showForm) {
return (
<div className="admin-change-pw">
<div className="admin-table--change-pw">
<input
className="form-control"
className="form-control input-xs"
name="password"
type="password"
value={user.password || ''}
placeholder="Password"
placeholder="New password"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
autoFocus={true}
@ -67,28 +67,29 @@ class ChangePassRow extends Component {
onConfirm={this.handleSubmit}
item={user}
onCancel={this.handleCancel}
buttonSize={buttonSize}
/>
</div>
)
}
return (
<button
className="btn btn-xs btn-info admin-table--hidden"
onClick={this.showForm}
>
Change Password
</button>
<div className="admin-table--change-pw">
<a href="#" onClick={this.showForm}>
Change
</a>
</div>
)
}
}
const {shape, func} = PropTypes
const {func, shape, string} = PropTypes
ChangePassRow.propTypes = {
user: shape().isRequired,
onApply: func.isRequired,
onEdit: func.isRequired,
buttonSize: string,
}
export default OnClickOutside(ChangePassRow)

View File

@ -25,7 +25,7 @@ const DatabaseManager = ({
onDeleteRetentionPolicy,
}) => {
return (
<div className="panel panel-info">
<div className="panel panel-default">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">
{databases.length === 1

View File

@ -1,7 +1,9 @@
import React, {PropTypes, Component} from 'react'
import onClickOutside from 'react-onclickoutside'
import {formatRPDuration} from 'utils/formatting'
import YesNoButtons from 'src/shared/components/YesNoButtons'
import onClickOutside from 'react-onclickoutside'
import {DATABASE_TABLE} from 'src/admin/constants/tableSizing'
class DatabaseRow extends Component {
constructor(props) {
@ -46,51 +48,55 @@ class DatabaseRow extends Component {
<tr>
<td>
{isNew
? <div className="admin-table--edit-cell">
<input
className="form-control"
type="text"
defaultValue={name}
placeholder="Name this RP"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.name = r)}
autoFocus={true}
/>
</div>
: <div className="admin-table--edit-cell">
{name}
</div>}
? <input
className="form-control input-xs"
type="text"
defaultValue={name}
placeholder="Name this RP"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.name = r)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
: name}
</td>
<td>
<div className="admin-table--edit-cell">
<input
className="form-control"
name="name"
type="text"
defaultValue={formattedDuration}
placeholder="How long should Data last"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.duration = r)}
autoFocus={!isNew}
/>
</div>
<td style={{width: `${DATABASE_TABLE.colDuration}px`}}>
<input
className="form-control input-xs"
name="name"
type="text"
defaultValue={formattedDuration}
placeholder="How long should Data last"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.duration = r)}
autoFocus={!isNew}
spellCheck={false}
autoComplete={false}
/>
</td>
<td style={isRFDisplayed ? {} : {display: 'none'}}>
<div className="admin-table--edit-cell">
<input
className="form-control"
name="name"
type="number"
min="1"
defaultValue={replication || 1}
placeholder="# of Nodes"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.replication = r)}
/>
</div>
</td>
<td className="text-right">
{isRFDisplayed
? <td style={{width: `${DATABASE_TABLE.colReplication}px`}}>
<input
className="form-control input-xs"
name="name"
type="number"
min="1"
defaultValue={replication || 1}
placeholder="# of Nodes"
onKeyDown={e => this.handleKeyDown(e, database)}
ref={r => (this.replication = r)}
spellCheck={false}
autoComplete={false}
/>
</td>
: null}
<td
className="text-right"
style={{width: `${DATABASE_TABLE.colDelete}px`}}
>
<YesNoButtons
buttonSize="btn-xs"
onConfirm={isNew ? this.handleCreate : this.handleUpdate}
onCancel={
isNew
@ -106,24 +112,37 @@ class DatabaseRow extends Component {
return (
<tr>
<td>
{name}
{' '}
{`${name} `}
{isDefault
? <span className="default-source-label">default</span>
: null}
</td>
<td onClick={this.handleStartEdit}>{formattedDuration}</td>
<td
onClick={this.handleStartEdit}
style={{width: `${DATABASE_TABLE.colDuration}px`}}
>
{formattedDuration}
</td>
{isRFDisplayed
? <td onClick={this.handleStartEdit}>{replication}</td>
? <td
onClick={this.handleStartEdit}
style={{width: `${DATABASE_TABLE.colReplication}px`}}
>
{replication}
</td>
: null}
<td className="text-right">
<td
className="text-right"
style={{width: `${DATABASE_TABLE.colDelete}px`}}
>
{isDeleting
? <YesNoButtons
onConfirm={() => onDelete(database, retentionPolicy)}
onCancel={this.handleEndDelete}
buttonSize="btn-xs"
/>
: <button
className="btn btn-xs btn-danger admin-table--hidden"
className="btn btn-danger btn-xs table--show-on-row-hover"
style={isDeletable ? {} : {visibility: 'hidden'}}
onClick={this.handleStartDelete}
>

View File

@ -1,9 +1,11 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import classnames from 'classnames'
import DatabaseRow from 'src/admin/components/DatabaseRow'
import DatabaseTableHeader from 'src/admin/components/DatabaseTableHeader'
import {DATABASE_TABLE} from 'src/admin/constants/tableSizing'
const {func, shape, bool} = PropTypes
@ -26,7 +28,11 @@ const DatabaseTable = ({
onDeleteRetentionPolicy,
}) => {
return (
<div className="db-manager">
<div
className={classnames('db-manager', {
'db-manager--edit': database.isEditing,
})}
>
<DatabaseTableHeader
database={database}
notify={notify}
@ -43,13 +49,21 @@ const DatabaseTable = ({
isAddRPDisabled={!!database.retentionPolicies.some(rp => rp.isNew)}
/>
<div className="db-manager-table">
<table className="table v-center admin-table">
<table className="table v-center table-highlight">
<thead>
<tr>
<th>Retention Policy</th>
<th>Duration</th>
{isRFDisplayed ? <th>Replication Factor</th> : null}
<th />
<th>
Retention Policy
</th>
<th style={{width: `${DATABASE_TABLE.colDuration}px`}}>
Duration
</th>
{isRFDisplayed
? <th style={{width: `${DATABASE_TABLE.colReplication}px`}}>
Replication Factor
</th>
: null}
<th style={{width: `${DATABASE_TABLE.colDelete}px`}} />
</tr>
</thead>
<tbody>

View File

@ -52,12 +52,6 @@ const Header = ({
onAddRetentionPolicy,
onDatabaseDeleteConfirm,
}) => {
const confirmStyle = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}
const buttons = (
<div className="text-right db-manager-header--actions">
<button
@ -87,23 +81,24 @@ const Header = ({
}
const deleteConfirmation = (
<div style={confirmStyle}>
<div className="admin-table--delete-cell">
<input
className="form-control"
name="name"
type="text"
value={database.deleteCode || ''}
placeholder={`DELETE ${database.name}`}
onChange={e => onDatabaseDeleteConfirm(database, e)}
onKeyDown={e => onDatabaseDeleteConfirm(database, e)}
autoFocus={true}
/>
</div>
<div className="admin-table--delete-db">
<input
className="form-control input-xs"
name="name"
type="text"
value={database.deleteCode || ''}
placeholder={`DELETE ${database.name}`}
onChange={e => onDatabaseDeleteConfirm(database, e)}
onKeyDown={e => onDatabaseDeleteConfirm(database, e)}
autoFocus={true}
autoComplete={false}
spellCheck={false}
/>
<ConfirmButtons
item={database}
onConfirm={onConfirm}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</div>
)
@ -119,14 +114,16 @@ const Header = ({
const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => (
<div className="db-manager-header db-manager-header--edit">
<input
className="form-control"
className="form-control input-sm"
name="name"
type="text"
value={database.name}
placeholder="Name this database"
placeholder="Name this Database"
onChange={e => onEdit(database, {name: e.target.value})}
onKeyDown={e => onKeyDown(e, database)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
<ConfirmButtons item={database} onConfirm={onConfirm} onCancel={onCancel} />
</div>

View File

@ -31,7 +31,7 @@ class FilterBar extends Component {
<div className="users__search-widget input-group admin__search-widget">
<input
type="text"
className="form-control"
className="form-control input-sm"
placeholder={`Filter ${placeholderText}...`}
value={this.state.filterText}
onChange={this.handleText}

View File

@ -1,18 +1,23 @@
import React, {PropTypes} from 'react'
import QueryRow from 'src/admin/components/QueryRow'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
const QueriesTable = ({queries, onKillQuery}) => (
<div>
<div className="panel panel-minimal">
<div className="panel panel-default">
<div className="panel-body">
<table className="table v-center admin-table">
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Database</th>
<th style={{width: `${QUERIES_TABLE.colDatabase}px`}}>
Database
</th>
<th>Query</th>
<th>Running</th>
<th />
<th style={{width: `${QUERIES_TABLE.colRunning}px`}}>
Running
</th>
<th style={{width: `${QUERIES_TABLE.colKillQuery}px`}} />
</tr>
</thead>
<tbody>

View File

@ -1,6 +1,7 @@
import React, {PropTypes, Component} from 'react'
import ConfirmButtons from 'src/shared/components/ConfirmButtons'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
class QueryRow extends Component {
constructor(props) {
@ -32,17 +33,31 @@ class QueryRow extends Component {
return (
<tr>
<td>{database}</td>
<td
style={{width: `${QUERIES_TABLE.colDatabase}px`}}
className="monotype"
>
{database}
</td>
<td><code>{query}</code></td>
<td>{duration}</td>
<td className="admin-table--kill-button text-right">
<td
style={{width: `${QUERIES_TABLE.colRunning}px`}}
className="monotype"
>
{duration}
</td>
<td
style={{width: `${QUERIES_TABLE.colKillQuery}px`}}
className="text-right"
>
{this.state.confirmingKill
? <ConfirmButtons
onConfirm={this.handleFinishHim}
onCancel={this.handleShowMercy}
buttonSize="btn-xs"
/>
: <button
className="btn btn-xs btn-danger admin-table--hidden"
className="btn btn-xs btn-danger table--show-on-row-hover"
onClick={this.handleInitiateKill}
>
Kill

View File

@ -1,5 +1,7 @@
import React, {Component, PropTypes} from 'react'
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
class RoleEditingRow extends Component {
constructor(props) {
super(props)
@ -25,19 +27,19 @@ class RoleEditingRow extends Component {
render() {
const {role} = this.props
return (
<td>
<div className="admin-table--edit-cell">
<input
className="form-control"
name="name"
type="text"
value={role.name || ''}
placeholder="role name"
onChange={this.handleEdit(role)}
onKeyPress={this.handleKeyPress(role)}
autoFocus={true}
/>
</div>
<td style={{width: `${ROLES_TABLE.colName}px`}}>
<input
className="form-control input-xs"
name="name"
type="text"
value={role.name || ''}
placeholder="Role name"
onChange={this.handleEdit(role)}
onKeyPress={this.handleKeyPress(role)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
</td>
)
}

View File

@ -1,11 +1,13 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import classnames from 'classnames'
import RoleEditingRow from 'src/admin/components/RoleEditingRow'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
const RoleRow = ({
role: {name, permissions, users},
@ -40,10 +42,18 @@ const RoleRow = ({
onSave={onSave}
isNew={isNew}
/>
<td />
<td />
<td className="text-right" style={{width: '85px'}}>
<ConfirmButtons item={role} onConfirm={onSave} onCancel={onCancel} />
<td className="admin-table--left-offset">--</td>
<td className="admin-table--left-offset">--</td>
<td
className="text-right"
style={{width: `${ROLES_TABLE.colDelete}px`}}
>
<ConfirmButtons
item={role}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
@ -51,7 +61,7 @@ const RoleRow = ({
return (
<tr>
<td>{name}</td>
<td style={{width: `${ROLES_TABLE.colName}px`}}>{name}</td>
<td>
{allPermissions && allPermissions.length
? <MultiSelectDropdown
@ -59,6 +69,14 @@ const RoleRow = ({
selectedItems={perms}
label={perms.length ? '' : 'Select Permissions'}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${ROLES_TABLE.colPermissions}`,
{
'admin-table--multi-select-empty': !permissions.length,
}
)}
/>
: null}
</td>
@ -69,10 +87,22 @@ const RoleRow = ({
selectedItems={users === undefined ? [] : users.map(u => u.name)}
label={users && users.length ? '' : 'Select Users'}
onApply={handleUpdateUsers}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${ROLES_TABLE.colUsers}`,
{
'admin-table--multi-select-empty': !users.length,
}
)}
/>
: null}
</td>
<DeleteConfirmTableCell onDelete={onDelete} item={role} />
<DeleteConfirmTableCell
onDelete={onDelete}
item={role}
buttonSize="btn-xs"
/>
</tr>
)
}

View File

@ -17,7 +17,7 @@ const RolesTable = ({
onUpdateRoleUsers,
onUpdateRolePermissions,
}) => (
<div className="panel panel-info">
<div className="panel panel-default">
<FilterBar
type="roles"
onFilter={onFilter}
@ -25,12 +25,12 @@ const RolesTable = ({
onClickCreate={onClickCreate}
/>
<div className="panel-body">
<table className="table v-center admin-table">
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
<th>Permissions</th>
<th>Users</th>
<th className="admin-table--left-offset">Permissions</th>
<th className="admin-table--left-offset">Users</th>
<th />
</tr>
</thead>

View File

@ -0,0 +1,56 @@
import React, {Component, PropTypes} from 'react'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserEditName extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
}
}
}
handleEdit(user) {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}
}
render() {
const {user} = this.props
return (
<td style={{width: `${USERS_TABLE.colUsername}px`}}>
<input
className="form-control input-xs"
name="name"
type="text"
value={user.name || ''}
placeholder="Username"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
</td>
)
}
}
const {func, shape} = PropTypes
UserEditName.propTypes = {
user: shape().isRequired,
onEdit: func.isRequired,
onSave: func.isRequired,
}
export default UserEditName

View File

@ -1,66 +0,0 @@
import React, {Component, PropTypes} from 'react'
class UserEditingRow extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
}
}
}
handleEdit(user) {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}
}
render() {
const {user, isNew} = this.props
return (
<td>
<div className="admin-table--edit-cell">
<input
className="form-control"
name="name"
type="text"
value={user.name || ''}
placeholder="Username"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
autoFocus={true}
/>
{isNew
? <input
className="form-control"
name="password"
type="password"
value={user.password || ''}
placeholder="Password"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
/>
: null}
</div>
</td>
)
}
}
const {bool, func, shape} = PropTypes
UserEditingRow.propTypes = {
user: shape().isRequired,
isNew: bool,
onEdit: func.isRequired,
onSave: func.isRequired,
}
export default UserEditingRow

View File

@ -0,0 +1,58 @@
import React, {Component, PropTypes} from 'react'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserNewPassword extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
}
}
}
handleEdit(user) {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}
}
render() {
const {user, isNew} = this.props
return (
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
{isNew
? <input
className="form-control input-xs"
name="password"
type="password"
value={user.password || ''}
placeholder="Password"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
spellCheck={false}
autoComplete={false}
/>
: '--'}
</td>
)
}
}
const {bool, func, shape} = PropTypes
UserNewPassword.propTypes = {
user: shape().isRequired,
isNew: bool,
onEdit: func.isRequired,
onSave: func.isRequired,
}
export default UserNewPassword

View File

@ -1,12 +1,15 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import classnames from 'classnames'
import UserEditingRow from 'src/admin/components/UserEditingRow'
import UserEditName from 'src/admin/components/UserEditName'
import UserNewPassword from 'src/admin/components/UserNewPassword'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
import ChangePassRow from 'src/admin/components/ChangePassRow'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
const UserRow = ({
user: {name, roles, permissions, password},
@ -42,16 +45,25 @@ const UserRow = ({
if (isEditing) {
return (
<tr className="admin-table--edit-row">
<UserEditingRow
<UserEditName user={user} onEdit={onEdit} onSave={onSave} />
<UserNewPassword
user={user}
onEdit={onEdit}
onSave={onSave}
isNew={isNew}
/>
{hasRoles ? <td /> : null}
<td />
<td className="text-right" style={{width: '85px'}}>
<ConfirmButtons item={user} onConfirm={onSave} onCancel={onCancel} />
{hasRoles ? <td className="admin-table--left-offset">--</td> : null}
<td className="admin-table--left-offset">--</td>
<td
className="text-right"
style={{width: `${USERS_TABLE.colDelete}px`}}
>
<ConfirmButtons
item={user}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
@ -59,7 +71,15 @@ const UserRow = ({
return (
<tr>
<td>{name}</td>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
onEdit={onEdit}
onApply={handleUpdatePassword}
user={user}
buttonSize="btn-xs"
/>
</td>
{hasRoles
? <td>
<MultiSelectDropdown
@ -71,6 +91,14 @@ const UserRow = ({
}
label={roles && roles.length ? '' : 'Select Roles'}
onApply={handleUpdateRoles}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${USERS_TABLE.colRoles}`,
{
'admin-table--multi-select-empty': !roles.length,
}
)}
/>
</td>
: null}
@ -83,17 +111,22 @@ const UserRow = ({
permissions && permissions.length ? '' : 'Select Permissions'
}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${USERS_TABLE.colPermissions}`,
{
'admin-table--multi-select-empty': !permissions.length,
}
)}
/>
: null}
</td>
<td className="text-right" style={{width: '300px'}}>
<ChangePassRow
onEdit={onEdit}
onApply={handleUpdatePassword}
user={user}
/>
</td>
<DeleteConfirmTableCell onDelete={onDelete} item={user} />
<DeleteConfirmTableCell
onDelete={onDelete}
item={user}
buttonSize="btn-xs"
/>
</tr>
)
}

View File

@ -20,7 +20,7 @@ const UsersTable = ({
onUpdateRoles,
onUpdatePassword,
}) => (
<div className="panel panel-info">
<div className="panel panel-default">
<FilterBar
type="users"
onFilter={onFilter}
@ -28,13 +28,13 @@ const UsersTable = ({
onClickCreate={onClickCreate}
/>
<div className="panel-body">
<table className="table v-center admin-table">
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>User</th>
{hasRoles && <th>Roles</th>}
<th>Permissions</th>
<th />
<th>Password</th>
{hasRoles && <th className="admin-table--left-offset">Roles</th>}
<th className="admin-table--left-offset">Permissions</th>
<th />
</tr>
</thead>

View File

@ -0,0 +1,27 @@
export const USERS_TABLE = {
colUsername: 240,
colPassword: 186,
colRoles: 190,
colPermissions: 190,
colDelete: 70,
}
export const ROLES_TABLE = {
colName: 280,
colUsers: 200,
colPermissions: 200,
colDelete: 70,
}
export const QUERIES_TABLE = {
colDatabase: 160,
colRunning: 80,
colKillQuery: 70,
}
export const ADMIN_TABLE = {
colDelete: 70,
}
export const DATABASE_TABLE = {
colRetentionPolicy: 140,
colDuration: 80,
colReplication: 122,
colDelete: 160,
}

View File

@ -89,7 +89,7 @@ const AlertsTable = React.createClass({
</div>
<div className="panel-body">
{this.props.alerts.length
? <table className="table v-center">
? <table className="table v-center table-highlight">
<thead>
<tr>
<th
@ -149,9 +149,9 @@ const AlertsTable = React.createClass({
</tbody>
</table>
: <div className="generic-empty-state">
<h4 className="no-user-select">
<h5 className="no-user-select">
Alerts appear here when you have Rules
</h4>
</h5>
<br />
<Link
to={`/sources/${id}/alert-rules/new`}
@ -201,7 +201,7 @@ const SearchBar = React.createClass({
value={this.state.searchTerm}
/>
<div className="input-group-addon">
<span className="icon search" aria-hidden="true" />
<span className="icon search" />
</div>
</div>
)

View File

@ -35,10 +35,10 @@ const DashboardHeader = ({
type="button"
data-toggle="dropdown"
>
<span className="button-text">{buttonText}</span>
<span>{buttonText}</span>
<span className="caret" />
</button>
<ul className="dropdown-menu" aria-labelledby="dropdownMenu1">
<ul className="dropdown-menu">
{children}
</ul>
</div>}
@ -55,7 +55,7 @@ const DashboardHeader = ({
: null}
{dashboard
? <button
className="btn btn-info btn-sm"
className="btn btn-default btn-sm"
onClick={onEditDashboard}
>
<span className="icon pencil" />
@ -64,7 +64,7 @@ const DashboardHeader = ({
: null}
{dashboard
? <div
className={classnames('btn btn-info btn-sm', {
className={classnames('btn btn-default btn-sm', {
active: showTemplateControlBar,
})}
onClick={onToggleTempVarControls}
@ -82,7 +82,7 @@ const DashboardHeader = ({
selected={timeRange}
/>
<div
className="btn btn-info btn-sm"
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
>
<span className="icon expand-a" style={{margin: 0}} />

View File

@ -36,15 +36,20 @@ class DashboardEditHeader extends Component {
return (
<div className="page-header full-width">
<div className="page-header__container">
<form className="page-header__left" onSubmit={this.handleFormSubmit}>
<form
className="page-header__left"
style={{flex: '1 0 0'}}
onSubmit={this.handleFormSubmit}
>
<input
className="page-header--editing"
name="name"
autoFocus={true}
value={name}
placeholder="Name this Dashboard"
onChange={e => this.handleChange(e.target.value)}
onKeyUp={this.handleKeyUp}
autoFocus={true}
spellCheck={false}
autoComplete="off"
/>
</form>

View File

@ -12,11 +12,11 @@ const OverlayControls = props => {
<h3 className="overlay--graph-name">Cell Editor</h3>
<div className="overlay-controls--right">
<p>Visualization Type:</p>
<ul className="toggle toggle-sm">
<ul className="nav nav-tablist nav-tablist-sm">
{graphTypes.map(graphType => (
<li
key={graphType.type}
className={classnames('toggle-btn', {
className={classnames({
active: graphType.type === selectedGraphType,
})}
onClick={() => onSelectGraphType(graphType.type)}

View File

@ -28,6 +28,7 @@ const TemplateControlBar = ({
<Dropdown
items={items}
buttonSize="btn-xs"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selectedText || '(No values)'}
onChoose={item =>

View File

@ -84,7 +84,7 @@ const RowButtons = ({
<div className="tvm-actions">
<DeleteConfirmButtons onDelete={() => onDelete(id)} />
<button
className="btn btn-sm btn-info btn-edit"
className="btn btn-sm btn-info btn-edit btn-square"
type="button"
onClick={e => {
// prevent subsequent 'onSubmit' that is caused by an unknown source,

View File

@ -304,10 +304,7 @@ class DashboardPage extends Component {
{dashboards
? dashboards.map((d, i) => (
<li className="dropdown-item" key={i}>
<Link
to={`/sources/${sourceID}/dashboards/${d.id}`}
className="role-option"
>
<Link to={`/sources/${sourceID}/dashboards/${d.id}`}>
{d.name}
</Link>
</li>

View File

@ -89,7 +89,7 @@ const DashboardsPage = React.createClass({
</div>
<div className="panel-body">
{dashboards && dashboards.length
? <table className="table v-center admin-table">
? <table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
@ -109,6 +109,7 @@ const DashboardsPage = React.createClass({
<DeleteConfirmTableCell
onDelete={this.handleDeleteDashboard}
item={dashboard}
buttonSize="btn-xs"
/>
</tr>
))}

View File

@ -1,7 +1,8 @@
import React, {PropTypes} from 'react'
import FieldListItem from 'src/data_explorer/components/FieldListItem'
import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown'
import GroupByTimeDropdown
from 'src/data_explorer/components/GroupByTimeDropdown'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {showFieldKeys} from 'shared/apis/metaQuery'
@ -77,7 +78,7 @@ const FieldList = React.createClass({
},
render() {
const {query} = this.props
const {query, isKapacitorRule} = this.props
const hasAggregates = query.fields.some(f => f.funcs && f.funcs.length)
const hasGroupByTime = query.groupBy.time
@ -90,6 +91,7 @@ const FieldList = React.createClass({
isOpen={!hasGroupByTime}
selected={query.groupBy.time}
onChooseGroupByTime={this.handleGroupByTime}
isInRuleBuilder={isKapacitorRule}
/>
: null}
</div>

View File

@ -56,7 +56,9 @@ const FieldListItem = React.createClass({
if (isKapacitorRule) {
return (
<div
className={classnames('query-builder--list-item', {active: isSelected})}
className={classnames('query-builder--list-item', {
active: isSelected,
})}
key={fieldFunc}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
@ -66,14 +68,16 @@ const FieldListItem = React.createClass({
</span>
{isSelected
? <Dropdown
className="dropdown-110"
menuClass="dropdown-malachite"
buttonSize="btn-xs"
items={items}
onChoose={this.handleApplyFunctions}
selected={
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
}
/>
: null
}
: null}
</div>
)
}
@ -81,7 +85,9 @@ const FieldListItem = React.createClass({
return (
<div key={fieldFunc}>
<div
className={classnames('query-builder--list-item', {active: isSelected})}
className={classnames('query-builder--list-item', {
active: isSelected,
})}
onClick={_.wrap(fieldFunc, this.handleToggleField)}
>
<span>
@ -89,13 +95,17 @@ const FieldListItem = React.createClass({
{fieldText}
</span>
{isSelected
? <div className={classnames('btn btn-xs btn-info', {'function-selector--toggled': isOpen})} onClick={this.toggleFunctionsMenu}>
? <div
className={classnames('btn btn-xs btn-default', {
'function-selector--toggled': isOpen,
})}
onClick={this.toggleFunctionsMenu}
>
Functions
</div>
: null
}
: null}
</div>
{(isSelected && isOpen)
{isSelected && isOpen
? <FunctionSelector
onApply={this.handleApplyFunctions}
selectedItems={fieldFunc.funcs || []}

View File

@ -1,49 +1,33 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import groupByTimeOptions from 'hson!../data/groupByTimes.hson'
const {bool, string, func} = PropTypes
import Dropdown from 'shared/components/Dropdown'
const {bool, func, string} = PropTypes
const GroupByTimeDropdown = React.createClass({
propTypes: {
isOpen: bool,
selected: string,
onChooseGroupByTime: func.isRequired,
isInRuleBuilder: bool,
},
render() {
const {isOpen, selected, onChooseGroupByTime} = this.props
const {selected, onChooseGroupByTime, isInRuleBuilder} = this.props
return (
<div
className={classnames('dropdown group-by-time-dropdown', {
open: isOpen,
})}
>
<div
className="btn btn-sm btn-info dropdown-toggle"
data-toggle="dropdown"
>
<span>Group by {selected || 'time'}</span>
<span className="caret" />
</div>
<ul className="dropdown-menu">
{groupByTimeOptions.map(groupBy => {
return (
<li
className="dropdown-item"
key={groupBy.menuOption}
onClick={() => onChooseGroupByTime(groupBy)}
>
<a href="#">
{groupBy.menuOption}
</a>
</li>
)
})}
</ul>
</div>
<Dropdown
className="dropdown-130"
menuClass={isInRuleBuilder ? 'dropdown-malachite' : null}
buttonColor={isInRuleBuilder ? 'btn-default' : 'btn-info'}
items={groupByTimeOptions.map(groupBy => ({
...groupBy,
text: groupBy.menuOption,
}))}
onChoose={onChooseGroupByTime}
selected={selected ? `Group by ${selected}` : 'Group by Time'}
/>
)
},
})

View File

@ -96,6 +96,8 @@ const MeasurementList = React.createClass({
value={this.state.filterText}
onChange={this.handleFilterText}
onKeyUp={this.handleEscape}
spellCheck={false}
autoComplete={false}
/>
<span className="icon search" />
</div>

View File

@ -254,7 +254,8 @@ class QueryEditor extends Component {
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
className="dropdown-140 query-editor--templates"
buttonSize="btn-xs"
/>
: null}
</div>
@ -270,7 +271,8 @@ class QueryEditor extends Component {
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
className="dropdown-140 query-editor--templates"
buttonSize="btn-xs"
/>
: null}
</div>
@ -300,7 +302,8 @@ class QueryEditor extends Component {
items={QUERY_TEMPLATES}
selected={'Query Templates'}
onChoose={this.handleChooseTemplate}
className="query-editor--templates"
className="dropdown-140 query-editor--templates"
buttonSize="btn-xs"
/>
: null}
</div>

View File

@ -66,6 +66,8 @@ const TagListItem = React.createClass({
value={this.state.filterText}
onChange={this.handleFilterText}
onKeyUp={this.handleEscape}
spellCheck={false}
autoComplete={false}
/>
<span className="icon search" />
</div>
@ -107,7 +109,7 @@ const TagListItem = React.createClass({
{tagItemLabel}
</span>
<div
className={classnames('btn btn-info btn-xs group-by-tag', {
className={classnames('btn btn-default btn-xs group-by-tag', {
active: this.props.isUsingGroupBy,
})}
onClick={this.handleGroupBy}

View File

@ -1,17 +1,18 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
const VisHeader = ({views, view, onToggleView, name}) => (
<div className="graph-heading">
{views.length
? <ul className="toggle toggle-sm">
? <ul className="nav nav-tablist nav-tablist-sm">
{views.map(v => (
<li
key={v}
onClick={() => onToggleView(v)}
className={classnames('toggle-btn ', {active: view === v})}
className={classnames({active: view === v})}
>
{v}
{_.upperFirst(v)}
</li>
))}
</ul>

View File

@ -108,8 +108,6 @@ const HostsTable = React.createClass({
hostsTitle = 'Loading Hosts...'
} else if (hostsError.length) {
hostsTitle = 'There was a problem loading hosts'
} else if (hosts.length === 0) {
hostsTitle = 'No hosts found'
} else if (hostCount === 1) {
hostsTitle = `${hostCount} Host`
} else {
@ -123,45 +121,52 @@ const HostsTable = React.createClass({
<SearchBar onSearch={this.updateSearchTerm} />
</div>
<div className="panel-body">
<table className="table v-center">
<thead>
<tr>
<th
onClick={() => this.updateSort('name')}
className={this.sortableClasses('name')}
>
Host
</th>
<th
onClick={() => this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: '74px'}}
>
Status
</th>
<th
onClick={() => this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: '70px'}}
>
CPU
</th>
<th
onClick={() => this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: '68px'}}
>
Load
</th>
<th>Apps</th>
</tr>
</thead>
<tbody>
{sortedHosts.map(h => {
return <HostRow key={h.name} host={h} source={source} />
})}
</tbody>
</table>
{hostCount > 0 && !hostsError.length
? <table className="table v-center table-highlight">
<thead>
<tr>
<th
onClick={() => this.updateSort('name')}
className={this.sortableClasses('name')}
>
Host
</th>
<th
onClick={() => this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: '74px'}}
>
Status
</th>
<th
onClick={() => this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: '70px'}}
>
CPU
</th>
<th
onClick={() => this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: '68px'}}
>
Load
</th>
<th>Apps</th>
</tr>
</thead>
<tbody>
{sortedHosts.map(h => {
return <HostRow key={h.name} host={h} source={source} />
})}
</tbody>
</table>
: <div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>
No Hosts found
</h4>
</div>}
</div>
</div>
)

View File

@ -190,21 +190,20 @@ export const HostPage = React.createClass({
>
{Object.keys(hosts).map((host, i) => {
return (
<li key={i}>
<Link
to={`/sources/${id}/hosts/${host + appParam}`}
className="role-option"
>
<li className="dropdown-item" key={i}>
<Link to={`/sources/${id}/hosts/${host + appParam}`}>
{host}
</Link>
</li>
)
})}
</DashboardHeader>
<FancyScrollbar className={classnames({
'page-contents': true,
'presentation-mode': inPresentationMode,
})}>
<FancyScrollbar
className={classnames({
'page-contents': true,
'presentation-mode': inPresentationMode,
})}
>
<div className="container-fluid full-width dashboard">
{layouts.length > 0 ? this.renderLayouts(layouts) : ''}
</div>

View File

@ -71,6 +71,21 @@ export function chooseTrigger(ruleID, trigger) {
}
}
export const addEvery = (ruleID, frequency) => ({
type: 'ADD_EVERY',
payload: {
ruleID,
frequency,
},
})
export const removeEvery = ruleID => ({
type: 'REMOVE_EVERY',
payload: {
ruleID,
},
})
export function updateRuleValues(ruleID, trigger, values) {
return {
type: 'UPDATE_RULE_VALUES',

View File

@ -6,6 +6,8 @@ import DatabaseList from '../../data_explorer/components/DatabaseList'
import MeasurementList from '../../data_explorer/components/MeasurementList'
import FieldList from '../../data_explorer/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
export const DataSection = React.createClass({
propTypes: {
source: PropTypes.shape({
@ -27,6 +29,8 @@ export const DataSection = React.createClass({
groupByTime: PropTypes.func.isRequired,
toggleTagAcceptance: PropTypes.func.isRequired,
}).isRequired,
onAddEvery: PropTypes.func.isRequired,
onRemoveEvery: PropTypes.func.isRequired,
timeRange: PropTypes.shape({}).isRequired,
},
@ -53,6 +57,11 @@ export const DataSection = React.createClass({
handleToggleField(field) {
this.props.actions.toggleField(this.props.query.id, field, true)
// Every is only added when a function has been added to a field.
// Here, the field is selected without a function.
this.props.onRemoveEvery()
// Because there are no functions there is no group by time.
this.props.actions.groupByTime(this.props.query.id, null)
},
handleGroupByTime(time) {
@ -61,6 +70,7 @@ export const DataSection = React.createClass({
handleApplyFuncsToField(fieldFunc) {
this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc)
this.props.onAddEvery(defaultEveryFrequency)
},
handleChooseTag(tag) {
@ -80,13 +90,13 @@ export const DataSection = React.createClass({
const statement = query.rawText || buildInfluxQLQuery({lower}, query)
return (
<div className="kapacitor-rule-section kapacitor-metric-selector">
<h3 className="rule-section-heading">Select a Time Series</h3>
<div className="rule-section-body">
<pre>
<div className="rule-section">
<h3 className="rule-section--heading">Select a Time Series</h3>
<div className="rule-section--body">
<pre className="rule-section--border-bottom">
<code
className={classnames({
'kapacitor-metric-placeholder': !statement,
'metric-placeholder': !statement,
})}
>
{statement || 'Build a query below'}

View File

@ -83,7 +83,7 @@ class KapacitorForm extends Component {
<div className="form-group form-group-submit col-xs-12 text-center">
<button
className="btn btn-info"
className="btn btn-default"
type="button"
onClick={onReset}
>

View File

@ -70,6 +70,8 @@ export const KapacitorRule = React.createClass({
source={source}
query={query}
actions={queryActions}
onAddEvery={this.handleAddEvery}
onRemoveEvery={this.handleRemoveEvery}
/>
<ValuesSection
rule={rule}
@ -149,6 +151,16 @@ export const KapacitorRule = React.createClass({
})
},
handleAddEvery(frequency) {
const {rule: {id: ruleID}, kapacitorActions: {addEvery}} = this.props
addEvery(ruleID, frequency)
},
handleRemoveEvery() {
const {rule: {id: ruleID}, kapacitorActions: {removeEvery}} = this.props
removeEvery(ruleID)
},
validationError() {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {

View File

@ -33,7 +33,7 @@ export const RuleGraph = React.createClass({
if (!queryText) {
return (
<div className="rule-preview--graph-empty">
<div className="rule-builder--graph-empty">
<p>Select a <strong>Time-Series</strong> to preview on a graph</p>
</div>
)

View File

@ -89,7 +89,7 @@ export const RuleHeader = React.createClass({
id="save-kapacitor-tooltip"
effect="solid"
html={true}
offset={{top: 2}}
offset={{bottom: 4}}
place="bottom"
class="influx-tooltip kapacitor-tooltip place-bottom"
/>
@ -110,31 +110,31 @@ export const RuleHeader = React.createClass({
onKeyDown={e => this.handleEditName(e, rule)}
onBlur={() => this.handleEditNameBlur(rule)}
placeholder="Name your rule"
spellCheck={false}
autoComplete={false}
/>
: <h1
className="page-header__title page-header--editable kapacitor-theme"
onClick={this.toggleEditName}
data-for="rename-kapacitor-tooltip"
data-tip="Click to Rename"
>
{rule.name}
<span className="icon pencil" />
<ReactTooltip
id="rename-kapacitor-tooltip"
delayShow={200}
effect="solid"
html={true}
offset={{top: 2}}
place="bottom"
class="influx-tooltip kapacitor-tooltip place-bottom"
/>
</h1>
: <div className="page-header__left">
<h1
className="page-header__title page-header--editable kapacitor-theme"
onClick={this.toggleEditName}
data-for="rename-kapacitor-tooltip"
data-tip="Click to Rename"
>
{rule.name}
<span className="icon pencil" />
<ReactTooltip
id="rename-kapacitor-tooltip"
delayShow={200}
effect="solid"
html={true}
offset={{top: 2}}
place="bottom"
class="influx-tooltip kapacitor-tooltip place-bottom"
/>
</h1>
</div>
return (
<div className="page-header__left">
{name}
</div>
)
return name
},
})

View File

@ -53,16 +53,16 @@ export const RuleMessage = React.createClass({
const selectedAlert = rule.alerts[0] || alerts[0].text
return (
<div className="kapacitor-rule-section">
<h3 className="rule-section-heading">Alert Message</h3>
<div className="rule-section-body">
<div className="kapacitor-values-tabs">
<div className="rule-section">
<h3 className="rule-section--heading">Alert Message</h3>
<div className="rule-section--body">
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send this Alert to:</p>
<ul className="btn-group btn-group-lg tab-group">
<ul className="nav nav-tablist nav-tablist-sm nav-tablist-malachite">
{alerts.map(alert => (
<li
key={alert.text}
className={classnames('btn tab', {
className={classnames({
active: alert.text === selectedAlert,
})}
onClick={() => this.handleChooseAlert(alert)}
@ -78,25 +78,27 @@ export const RuleMessage = React.createClass({
rule={rule}
/>
{selectedAlert === 'smtp'
? <div className="alert-message--email-body">
? <div className="rule-section--border-bottom">
<textarea
className="alert-text details"
className="form-control form-malachite monotype rule-builder--message"
placeholder="Email body text goes here"
ref={r => (this.details = r)}
onChange={() =>
actions.updateDetails(rule.id, this.details.value)}
value={rule.details}
spellCheck={false}
/>
</div>
: null}
<textarea
className="alert-text message"
className="form-control form-malachite monotype rule-builder--message"
ref={r => (this.message = r)}
onChange={() => actions.updateMessage(rule.id, this.message.value)}
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields &quot;value&quot; }}"
value={rule.message}
spellCheck={false}
/>
<div className="alert-message--formatting">
<div className="rule-section--row rule-section--row-last rule-section--border-top">
<p>Templates:</p>
{Object.keys(templates).map(t => {
return (
@ -137,7 +139,15 @@ const CodeData = React.createClass({
const {onClickTemplate, template} = this.props
const {label, text} = template
return <code data-tip={text} onClick={onClickTemplate}>{label}</code>
return (
<code
className="rule-builder--message-template"
data-tip={text}
onClick={onClickTemplate}
>
{label}
</code>
)
},
})

View File

@ -14,11 +14,12 @@ const RuleMessageAlertConfig = ({updateAlertNodes, alert, rule}) => {
return null
}
return (
<div className="rule-section--item alert-message--config">
<div className="rule-section--row rule-section--border-bottom">
<p>{DEFAULT_ALERT_LABELS[alert]}</p>
<input
id="alert-input"
className="form-control size-486 form-control--green input-sm"
className="form-control input-sm form-malachite"
style={{flex: '1 0 0'}}
type="text"
placeholder={DEFAULT_ALERT_PLACEHOLDERS[alert]}
onChange={e => updateAlertNodes(rule.id, alert, e.target.value)}

View File

@ -22,12 +22,14 @@ export const ValuesSection = React.createClass({
const initialIndex = TABS.indexOf(_.startCase(rule.trigger))
return (
<div className="kapacitor-rule-section">
<h3 className="rule-section-heading">Rule Conditions</h3>
<div className="rule-section-body">
<div className="rule-section">
<h3 className="rule-section--heading">Rule Conditions</h3>
<div className="rule-section--body">
<Tabs initialIndex={initialIndex} onSelect={this.handleChooseTrigger}>
<TabList isKapacitorTabs="true">
{TABS.map(tab => <Tab key={tab}>{tab}</Tab>)}
{TABS.map(tab => (
<Tab key={tab} isKapacitorTab={true}>{tab}</Tab>
))}
</TabList>
<TabPanels>
@ -99,29 +101,38 @@ const Threshold = React.createClass({
const operators = mapToItems(OPERATORS, 'operator')
return (
<div className="value-selector">
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert where</p>
<span>
<span className="rule-builder--metric">
{query.fields.length ? query.fields[0].field : 'Select a Time-Series'}
</span>
<p>is</p>
<Dropdown
className="size-176 dropdown-kapacitor"
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={this.handleDropdownChange}
/>
<input
className="form-control input-sm size-166 form-control--green"
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
type="text"
spellCheck="false"
ref={r => (this.valueInput = r)}
defaultValue={value}
onKeyUp={this.handleInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/>
{(operator === 'inside range' || operator === 'outside range') &&
<input
className="form-control input-sm size-166 form-control--green"
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck="false"
ref={r => (this.valueRangeInput = r)}
@ -162,30 +173,34 @@ const Relative = React.createClass({
const operators = mapToItems(OPERATORS, 'operator')
return (
<div className="value-selector">
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert when</p>
<Dropdown
className="size-106 dropdown-kapacitor"
className="dropdown-110"
menuClass="dropdown-malachite"
items={changes}
selected={change}
onChoose={this.handleDropdownChange}
/>
<p>compared to previous</p>
<Dropdown
className="size-66 dropdown-kapacitor"
className="dropdown-80"
menuClass="dropdown-malachite"
items={shifts}
selected={shift}
onChoose={this.handleDropdownChange}
/>
<p>is</p>
<Dropdown
className="size-176 dropdown-kapacitor"
className="dropdown-160"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={this.handleDropdownChange}
/>
<input
className="form-control input-sm size-166 form-control--green"
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
ref={r => (this.input = r)}
defaultValue={value}
onKeyUp={this.handleInputChange}
@ -193,7 +208,7 @@ const Relative = React.createClass({
type="text"
spellCheck="false"
/>
<p>{change === CHANGES[1] ? '%' : ''}</p>
{change === CHANGES[1] ? <p>%</p> : null}
</div>
)
},
@ -219,10 +234,11 @@ const Deadman = React.createClass({
})
return (
<div className="value-selector">
<div className="rule-section--row">
<p>Send Alert if Data is missing for</p>
<Dropdown
className="size-66 dropdown-kapacitor"
className="dropdown-80"
menuClass="dropdown-malachite"
items={periods}
selected={this.props.rule.values.period}
onChoose={this.handleChange}

View File

@ -14,18 +14,18 @@ class RedactedInput extends Component {
if (defaultValue === true && !editing) {
return (
<div className="alert-value-set">
<span>
value set
<a
href="#"
onClick={() => {
this.setState({editing: true})
}}
>
(change it)
</a>
<div className="form-control-static redacted-input">
<span className="alert-value-set">
<span className="icon checkmark" /> Value set
</span>
<button
className="btn btn-xs btn-link"
onClick={() => {
this.setState({editing: true})
}}
>
Change
</button>
<input
className="form-control"
id={id}

View File

@ -52,18 +52,21 @@ const TelegramConfig = React.createClass({
return (
<form onSubmit={this.handleSaveAlert}>
<p className="no-user-select">
You need a
{' '}
<a
href="https://docs.influxdata.com/kapacitor/v1.2/guides/event-handler-setup/#telegram-bot"
target="_blank"
>
Telegram Bot
</a>
{' '}
to use this endpoint
</p>
<div className="form-group col-xs-12">
<div className="alert alert-warning alert-icon no-user-select">
<span className="icon triangle" />
You need a
{' '}
<a
href="https://docs.influxdata.com/kapacitor/v1.2/guides/event-handler-setup/#telegram-bot"
target="_blank"
>
Telegram Bot
</a>
{' '}
to use this endpoint
</div>
</div>
<div className="form-group col-xs-12">
<label htmlFor="token">
Token
@ -100,7 +103,7 @@ const TelegramConfig = React.createClass({
<div className="form-group col-xs-12">
<label htmlFor="parseMode">Select the alert message format</label>
<div className="form-control-static">
<div className="radio">
<div className="radio-item">
<input
id="parseModeMarkdown"
type="radio"
@ -111,7 +114,7 @@ const TelegramConfig = React.createClass({
/>
<label htmlFor="parseModeMarkdown">Markdown</label>
</div>
<div className="radio">
<div className="radio-item">
<input
id="parseModeHTML"
type="radio"

View File

@ -19,6 +19,8 @@ export const defaultRuleConfigs = {
},
}
export const defaultEveryFrequency = '30s'
export const OPERATORS = [
'greater than',
'equal to or greater',
@ -80,7 +82,7 @@ export const DEFAULT_ALERT_LABELS = {
http: 'URL:',
tcp: 'Address:',
exec: 'Add Command (Arguments separated by Spaces):',
log: 'File',
log: 'File:',
smtp: 'Email Addresses (Separated by Spaces):',
slack: 'Send alerts to Slack channel:',
alerta: 'Paste Alerta TICKscript:',

View File

@ -1,8 +1,10 @@
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
import * as kapacitorActionCreators from '../actions/view'
import * as queryActionCreators from '../../data_explorer/actions/view'
import * as kapacitorActionCreators from 'src/kapacitor/actions/view'
import * as queryActionCreators from 'src/data_explorer/actions/view'
import {bindActionCreators} from 'redux'
import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index'
import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants'
@ -23,6 +25,8 @@ export const KapacitorRulePage = React.createClass({
loadDefaultRule: PropTypes.func.isRequired,
fetchRule: PropTypes.func.isRequired,
chooseTrigger: PropTypes.func.isRequired,
addEvery: PropTypes.func.isRequired,
removeEvery: PropTypes.func.isRequired,
updateRuleValues: PropTypes.func.isRequired,
updateMessage: PropTypes.func.isRequired,
updateAlerts: PropTypes.func.isRequired,
@ -76,7 +80,7 @@ export const KapacitorRulePage = React.createClass({
.catch(() => {
addFlashMessage({
type: 'error',
text: 'We couldn\'t find a configured Kapacitor for this source',
text: "We couldn't find a configured Kapacitor for this source", // eslint-disable-line quotes
})
})
})

View File

@ -15,7 +15,7 @@ export default function rules(state = {}, action) {
message: '',
alerts: [],
alertNodes: [],
every: '30s',
every: null,
name: 'Untitled Rule',
},
})
@ -45,6 +45,16 @@ export default function rules(state = {}, action) {
})
}
case 'ADD_EVERY': {
const {ruleID, frequency} = action.payload
return {...state, [ruleID]: {...state[ruleID], every: frequency}}
}
case 'REMOVE_EVERY': {
const {ruleID} = action.payload
return {...state, [ruleID]: {...state[ruleID], every: null}}
}
case 'UPDATE_RULE_VALUES': {
const {ruleID, trigger, values} = action.payload
return Object.assign({}, state, {

View File

@ -46,7 +46,7 @@ const AutoRefreshDropdown = React.createClass({
return (
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
<div
className="btn btn-sm btn-info dropdown-toggle"
className="btn btn-sm btn-default dropdown-toggle"
onClick={() => self.toggleMenu()}
>
<span

View File

@ -1,11 +1,22 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
const ConfirmButtons = ({onConfirm, item, onCancel}) => (
const ConfirmButtons = ({onConfirm, item, onCancel, buttonSize}) => (
<div className="confirm-buttons">
<button className="btn btn-xs btn-info" onClick={() => onCancel(item)}>
<button
className={classnames('btn btn-info btn-square', {
[buttonSize]: buttonSize,
})}
onClick={() => onCancel(item)}
>
<span className="icon remove" />
</button>
<button className="btn btn-xs btn-success" onClick={() => onConfirm(item)}>
<button
className={classnames('btn btn-success btn-square', {
[buttonSize]: buttonSize,
})}
onClick={() => onConfirm(item)}
>
<span className="icon checkmark" />
</button>
</div>
@ -17,6 +28,10 @@ ConfirmButtons.propTypes = {
onConfirm: func.isRequired,
item: oneOfType([shape(), string]),
onCancel: func.isRequired,
buttonSize: string,
}
ConfirmButtons.defaultProps = {
buttonSize: 'btn-sm',
}
export default ConfirmButtons

View File

@ -46,15 +46,15 @@ class CustomTimeRangeDropdown extends Component {
return (
<div
className={classnames('custom-time-range', {show: isVisible})}
className={classnames('custom-time-range', {open: isVisible})}
style={{display: 'flex'}}
>
<button
className="btn btn-sm btn-info custom-time-range--btn"
className="btn btn-sm btn-default dropdown-toggle"
onClick={onToggle}
>
<span className="icon clock" />
{`${moment(lower).format('MMM Do HH:mm')}${moment(upper).format('MMM Do HH:mm')}`}
<span className="dropdown-selected">{`${moment(lower).format('MMM Do HH:mm')}${moment(upper).format('MMM Do HH:mm')}`}</span>
<span className="caret" />
</button>
<div className="custom-time--container">

View File

@ -1,11 +1,14 @@
import React, {PropTypes, Component} from 'react'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import ConfirmButtons from 'shared/components/ConfirmButtons'
const DeleteButton = ({onClickDelete}) => (
const DeleteButton = ({onClickDelete, buttonSize}) => (
<button
className="btn btn-xs btn-danger admin-table--hidden"
className={classnames('btn btn-danger table--show-on-row-hover', {
[buttonSize]: buttonSize,
})}
onClick={onClickDelete}
>
Delete
@ -35,7 +38,7 @@ class DeleteConfirmButtons extends Component {
}
render() {
const {onDelete, item} = this.props
const {onDelete, item, buttonSize} = this.props
const {isConfirming} = this.state
return isConfirming
@ -43,8 +46,12 @@ class DeleteConfirmButtons extends Component {
onConfirm={onDelete}
item={item}
onCancel={this.handleCancel}
buttonSize={buttonSize}
/>
: <DeleteButton
onClickDelete={this.handleClickDelete}
buttonSize={buttonSize}
/>
: <DeleteButton onClickDelete={this.handleClickDelete} />
}
}
@ -52,11 +59,17 @@ const {func, oneOfType, shape, string} = PropTypes
DeleteButton.propTypes = {
onClickDelete: func.isRequired,
buttonSize: string,
}
DeleteConfirmButtons.propTypes = {
item: oneOfType([(string, shape())]),
onDelete: func.isRequired,
buttonSize: string,
}
DeleteConfirmButtons.defaultProps = {
buttonSize: 'btn-sm',
}
export default OnClickOutside(DeleteConfirmButtons)

View File

@ -1,8 +1,10 @@
import React from 'react'
import DeleteConfirmButtons from 'shared/components/DeleteConfirmButtons'
import {ADMIN_TABLE} from 'src/admin/constants/tableSizing'
const DeleteConfirmTableCell = props => (
<td className="text-right" style={{width: '85px'}}>
<td className="text-right" style={{width: `${ADMIN_TABLE.colDelete}px`}}>
<DeleteConfirmButtons {...props} />
</td>
)

View File

@ -3,10 +3,7 @@ import {Link} from 'react-router'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {
DROPDOWN_MENU_MAX_HEIGHT,
DROPDOWN_MENU_ITEM_THRESHOLD,
} from 'shared/constants/index'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
class Dropdown extends Component {
constructor(props) {
@ -32,7 +29,7 @@ class Dropdown extends Component {
static defaultProps = {
actions: [],
buttonSize: 'btn-sm',
buttonColor: 'btn-info',
buttonColor: 'btn-default',
menuWidth: '100%',
useAutoComplete: false,
}
@ -126,13 +123,14 @@ class Dropdown extends Component {
})
}
renderShortMenu() {
renderMenu() {
const {
actions,
addNew,
items,
menuWidth,
menuLabel,
menuClass,
useAutoComplete,
selected,
} = this.props
@ -143,86 +141,19 @@ class Dropdown extends Component {
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
style={{width: menuWidth}}
>
{menuLabel ? <li className="dropdown-header">{menuLabel}</li> : null}
{menuItems.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} role="separator" className="divider" />
}
return (
<li
className={classnames('dropdown-item', {
highlight: i === highlightedItemIndex,
active: item.text === selected,
})}
key={i}
>
<a
href="#"
onClick={() => this.handleSelection(item)}
onMouseOver={() => this.handleHighlight(i)}
>
{item.text}
</a>
{actions.length > 0
? <div className="dropdown-item__actions">
{actions.map(action => {
return (
<button
key={action.text}
className="dropdown-item__action"
onClick={e => this.handleAction(e, action, item)}
>
<span
title={action.text}
className={`icon ${action.icon}`}
/>
</button>
)
})}
</div>
: null}
</li>
)
})}
{addNew
? <li className="dropdown-item">
<Link to={addNew.url}>
{addNew.text}
</Link>
</li>
: null}
</ul>
)
}
renderLongMenu() {
const {
actions,
addNew,
items,
menuWidth,
menuLabel,
useAutoComplete,
selected,
} = this.props
const {filteredItems, highlightedItemIndex} = this.state
const menuItems = useAutoComplete ? filteredItems : items
return (
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
})}
style={{width: menuWidth, height: DROPDOWN_MENU_MAX_HEIGHT}}
>
<FancyScrollbar autoHide={false}>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
{menuLabel ? <li className="dropdown-header">{menuLabel}</li> : null}
{menuItems.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} role="separator" className="divider" />
return <li key={i} className="dropdown-divider" />
}
return (
<li
@ -240,12 +171,12 @@ class Dropdown extends Component {
{item.text}
</a>
{actions.length > 0
? <div className="dropdown-item__actions">
? <div className="dropdown-actions">
{actions.map(action => {
return (
<button
key={action.text}
className="dropdown-item__action"
className="dropdown-action"
onClick={e => this.handleAction(e, action, item)}
>
<span
@ -261,8 +192,8 @@ class Dropdown extends Component {
)
})}
{addNew
? <li>
<Link to={addNew.url}>
? <li className="multi-select--apply">
<Link className="btn btn-xs btn-default" to={addNew.url}>
{addNew.text}
</Link>
</li>
@ -277,6 +208,7 @@ class Dropdown extends Component {
items,
selected,
className,
menuClass,
iconName,
buttonSize,
buttonColor,
@ -288,7 +220,10 @@ class Dropdown extends Component {
return (
<div
onClick={this.handleClick}
className={classnames(`dropdown ${className}`, {open: isOpen})}
className={classnames('dropdown', {
open: isOpen,
[className]: className,
})}
>
{useAutoComplete && isOpen
? <div
@ -314,14 +249,14 @@ class Dropdown extends Component {
<span className="dropdown-selected">{selected}</span>
<span className="caret" />
</div>}
{isOpen && menuItems.length < DROPDOWN_MENU_ITEM_THRESHOLD
? this.renderShortMenu()
: null}
{isOpen && menuItems.length >= DROPDOWN_MENU_ITEM_THRESHOLD
? this.renderLongMenu()
: null}
{isOpen && menuItems.length ? this.renderMenu() : null}
{isOpen && !menuItems.length
? <ul className="dropdown-menu">
? <ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
>
<li className="dropdown-empty">No matching items</li>
</ul>
: null}
@ -358,6 +293,7 @@ Dropdown.propTypes = {
buttonColor: string,
menuWidth: string,
menuLabel: string,
menuClass: string,
useAutoComplete: bool,
}

View File

@ -9,21 +9,34 @@ class FancyScrollbar extends Component {
static defaultProps = {
autoHide: true,
autoHeight: false,
}
render() {
const {autoHide, children, className} = this.props
const {autoHide, autoHeight, children, className, maxHeight} = this.props
return (
<Scrollbars
className={classnames('fancy-scroll--container', {[className]: className})}
className={classnames('fancy-scroll--container', {
[className]: className,
})}
autoHide={autoHide}
autoHideTimeout={1000}
autoHideDuration={250}
renderTrackHorizontal={props => <div {...props} className="fancy-scroll--track-h"/>}
renderTrackVertical={props => <div {...props} className="fancy-scroll--track-v"/>}
renderThumbHorizontal={props => <div {...props} className="fancy-scroll--thumb-h"/>}
renderThumbVertical={props => <div {...props} className="fancy-scroll--thumb-v"/>}
autoHeight={autoHeight}
autoHeightMax={maxHeight}
renderTrackHorizontal={props => (
<div {...props} className="fancy-scroll--track-h" />
)}
renderTrackVertical={props => (
<div {...props} className="fancy-scroll--track-v" />
)}
renderThumbHorizontal={props => (
<div {...props} className="fancy-scroll--thumb-h" />
)}
renderThumbVertical={props => (
<div {...props} className="fancy-scroll--thumb-v" />
)}
>
{children}
</Scrollbars>
@ -31,12 +44,14 @@ class FancyScrollbar extends Component {
}
}
const {bool, node, string} = PropTypes
const {bool, node, number, string} = PropTypes
FancyScrollbar.propTypes = {
children: node.isRequired,
className: string,
autoHide: bool,
autoHeight: bool,
maxHeight: number,
}
export default FancyScrollbar

View File

@ -83,7 +83,7 @@ export const LayoutRenderer = React.createClass({
return text
},
renderRefreshingGraph(type, queries) {
renderRefreshingGraph(type, queries, cellHeight) {
const {timeRange, autoRefresh, templates} = this.props
if (type === 'single-stat') {
@ -92,6 +92,7 @@ export const LayoutRenderer = React.createClass({
queries={[queries[0]]}
templates={templates}
autoRefresh={autoRefresh}
cellHeight={cellHeight}
/>
)
}
@ -158,7 +159,7 @@ export const LayoutRenderer = React.createClass({
shouldNotBeEditable={shouldNotBeEditable}
cell={cell}
>
{this.renderRefreshingGraph(cell.type, queries)}
{this.renderRefreshingGraph(cell.type, queries, cell.h)}
</NameableGraph>
</div>
)

View File

@ -1,8 +1,12 @@
import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import classnames from 'classnames'
import _ from 'lodash'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
const labelText = ({localSelectedItems, isOpen, label}) => {
if (label) {
return label
@ -67,20 +71,18 @@ class MultiSelectDropdown extends Component {
render() {
const {localSelectedItems, isOpen} = this.state
const {label} = this.props
const {label, buttonSize, buttonColor, customClass, iconName} = this.props
return (
<div
className={classnames('dropdown multi-select-dropdown', {open: isOpen})}
>
<div className={classnames(`dropdown ${customClass}`, {open: isOpen})}>
<div
onClick={::this.toggleMenu}
className="btn btn-xs btn-info dropdown-toggle"
type="button"
className={`dropdown-toggle btn ${buttonSize} ${buttonColor}`}
>
<div className="multi-select-dropdown__label">
{iconName ? <span className={`icon ${iconName}`} /> : null}
<span className="dropdown-selected">
{labelText({localSelectedItems, isOpen, label})}
</div>
</span>
<span className="caret" />
</div>
{this.renderMenu()}
@ -92,33 +94,36 @@ class MultiSelectDropdown extends Component {
const {items} = this.props
return (
<div className="dropdown-options">
<div
className="multi-select-dropdown__apply"
onClick={this.onApplyFunctions}
style={{listStyle: 'none'}}
>
<div className="btn btn-xs btn-info btn-block">Apply</div>
</div>
<ul
className="dropdown-menu multi-select-dropdown__menu"
aria-labelledby="dropdownMenu1"
<ul className="dropdown-menu">
<li className="multi-select--apply">
<button
className="btn btn-xs btn-info"
onClick={this.onApplyFunctions}
>
Apply
</button>
</li>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
{items.map((listItem, i) => {
return (
<li
key={i}
className={classnames('multi-select-dropdown__item', {
active: this.isSelected(listItem),
className={classnames('multi-select--item', {
checked: this.isSelected(listItem),
})}
onClick={_.wrap(listItem, this.onSelect)}
>
<a href="#">{listItem}</a>
<div className="multi-select--checkbox" />
<span>{listItem}</span>
</li>
)
})}
</ul>
</div>
</FancyScrollbar>
</ul>
)
}
}
@ -130,6 +135,15 @@ MultiSelectDropdown.propTypes = {
items: arrayOf(string.isRequired).isRequired,
selectedItems: arrayOf(string.isRequired).isRequired,
label: string,
buttonSize: string,
buttonColor: string,
customClass: string,
iconName: string,
}
MultiSelectDropdown.defaultProps = {
buttonSize: 'btn-sm',
buttonColor: 'btn-default',
customClass: 'dropdown-160',
}
export default OnClickOutside(MultiSelectDropdown)

View File

@ -1,13 +1,17 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import shallowCompare from 'react-addons-shallow-compare'
import lastValues from 'src/shared/parsing/lastValues'
const SMALL_CELL_HEIGHT = 1
export default React.createClass({
displayName: 'LineGraph',
propTypes: {
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
title: PropTypes.string,
isFetchingInitially: PropTypes.bool,
cellHeight: PropTypes.number,
},
shouldComponentUpdate(nextProps, nextState) {
@ -15,7 +19,7 @@ export default React.createClass({
},
render() {
const {data} = this.props
const {data, cellHeight} = this.props
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
if (this.props.isFetchingInitially) {
@ -33,7 +37,13 @@ export default React.createClass({
return (
<div className="single-stat">
{roundedValue}
<span
className={classnames('single-stat--value', {
'single-stat--small': cellHeight === SMALL_CELL_HEIGHT,
})}
>
{roundedValue}
</span>
</div>
)
},

View File

@ -8,9 +8,20 @@ export const Tab = React.createClass({
onClick: func,
isDisabled: bool,
isActive: bool,
isKapacitorTab: bool,
},
render() {
if (this.props.isKapacitorTab) {
return (
<li
className={classnames({active: this.props.isActive})}
onClick={this.props.isDisabled ? null : this.props.onClick}
>
{this.props.children}
</li>
)
}
return (
<div
className={classnames('btn tab', {active: this.props.isActive})}
@ -47,9 +58,11 @@ export const TabList = React.createClass({
if (this.props.isKapacitorTabs === 'true') {
return (
<div className="kapacitor-values-tabs">
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Alert Type</p>
<div className="btn-group btn-group-lg tab-group">{children}</div>
<div className="nav nav-tablist nav-tablist-sm nav-tablist-malachite">
{children}
</div>
</div>
)
}

View File

@ -6,6 +6,7 @@ import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import timeRanges from 'hson!../data/timeRanges.hson'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
const TimeRangeDropdown = React.createClass({
autobind: false,
@ -58,7 +59,7 @@ const TimeRangeDropdown = React.createClass({
return (
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
<div
className="btn btn-sm btn-info dropdown-toggle"
className="btn btn-sm btn-default dropdown-toggle"
onClick={() => self.toggleMenu()}
>
<span className="icon clock" />
@ -67,8 +68,12 @@ const TimeRangeDropdown = React.createClass({
</span>
<span className="caret" />
</div>
<ul className="dropdown-menu" style={{height: '270px'}}>
<FancyScrollbar>
<ul className="dropdown-menu">
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
<li className="dropdown-header">Time Range</li>
{timeRanges.map(item => {
return (

View File

@ -1,21 +1,36 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
const YesNoButtons = ({onConfirm, onCancel}) => (
const YesNoButtons = ({onConfirm, onCancel, buttonSize}) => (
<div>
<button className="btn btn-xs btn-info" onClick={onCancel}>
<button
className={classnames('btn btn-square btn-info', {
[buttonSize]: buttonSize,
})}
onClick={onCancel}
>
<span className="icon remove" />
</button>
<button className="btn btn-xs btn-success" onClick={onConfirm}>
<button
className={classnames('btn btn-square btn-success', {
[buttonSize]: buttonSize,
})}
onClick={onConfirm}
>
<span className="icon checkmark" />
</button>
</div>
)
const {func} = PropTypes
const {func, string} = PropTypes
YesNoButtons.propTypes = {
onConfirm: func.isRequired,
onCancel: func.isRequired,
buttonSize: string,
}
YesNoButtons.defaultProps = {
buttonSize: 'btn-sm',
}
export default YesNoButtons

View File

@ -379,8 +379,7 @@ export const STROKE_WIDTH = {
light: 1.5,
}
export const DROPDOWN_MENU_MAX_HEIGHT = '270px'
export const DROPDOWN_MENU_ITEM_THRESHOLD = 10
export const DROPDOWN_MENU_MAX_HEIGHT = 240
export const HEARTBEAT_INTERVAL = 10000 // ms

View File

@ -34,8 +34,8 @@ const kapacitorDropdown = (
return (
<Dropdown
className="dropdown-160"
buttonColor="btn-info"
className="dropdown-260"
buttonColor="btn-default"
buttonSize="btn-xs"
items={kapacitorItems}
onChoose={item => setActiveKapacitor(item.kapacitor)}
@ -87,7 +87,7 @@ const InfluxTable = ({
</Link>
</div>
<div className="panel-body">
<table className="table v-center margin-bottom-zero">
<table className="table v-center margin-bottom-zero table-highlight">
<thead>
<tr>
<th>Name</th>
@ -131,7 +131,7 @@ const InfluxTable = ({
Connect
</Link>}
<button
className="btn btn-danger btn-xs"
className="btn btn-danger btn-xs btn-square"
onClick={() => handleDeleteSource(s)}
>
<span className="icon trash" />

View File

@ -9,7 +9,6 @@
// Base Theme
@import 'theme/bootstrap';
@import 'theme/bootstrap-theme';
@import 'theme/theme-dark';
@import 'theme/reset';
// External
@ -23,18 +22,16 @@
@import 'layout/page-header';
@import 'layout/sidebar';
@import 'layout/overlay';
@import 'layout/flash-messages';
// Components
@import 'components/confirm-buttons';
@import 'components/custom-time-range';
@import 'components/dropdown';
@import 'components/dygraphs';
@import 'components/flash-messages';
@import 'components/flip-toggle';
@import 'components/graph-tips';
@import 'components/graph';
@import 'components/input-tag-list';
@import 'components/multi-select-dropdown';
@import 'components/page-header-dropdown';
@import 'components/page-header-editable';
@import 'components/page-spinner';

View File

@ -15,7 +15,4 @@
margin: 0 !important;
}
}
span.icon {
margin: 0 !important;
}
}
}

View File

@ -6,16 +6,6 @@
.custom-time-range {
position: relative;
}
.btn.btn-sm.btn-info.custom-time-range--btn {
padding: 0 30px 0 9px !important;
.caret {
position: absolute;
right: 9px;
top: calc(50% + 1px);
transform: translateY(-50%);
}
}
.custom-time--container {
display: none;
position: absolute;
@ -242,8 +232,8 @@ $rd-cell-size: 30px;
}
/* Show State */
.custom-time-range.show {
/* Open State */
.custom-time-range.open {
.custom-time--container {
display: flex;
}
@ -251,4 +241,4 @@ $rd-cell-size: 30px;
color: $g20-white !important;
background-color: $g6-smoke;
}
}
}

View File

@ -1,343 +0,0 @@
/*
Dropdowns
----------------------------------------------
Note: Some of these styles are intended to
override styles from bootstrap-theme
*/
$dropdown-menu-default-width: 100%;
$dropdown-menu-max-height: 270px;
/*
Generic width modifiers
Use instead of creating new classes if possible
*/
.dropdown .dropdown-toggle,
.dropdown .dropdown-autocomplete {
width: 120px; /* Default width */
}
.dropdown {
@for $i from 8 through 30 {
&-#{$i * 10} .dropdown-toggle,
&-#{$i * 10} .dropdown-autocomplete { width: #{$i * 10}px; }
}
}
.dropdown-toggle {
position: relative;
text-align: left;
display: flex;
align-items: center;
.caret {
position: absolute;
top: calc(50% + 1px);
right: 8px;
transform: translateY(-50%);
}
> .icon {
display: inline-block;
vertical-align: middle;
margin-right: 6px;
}
.dropdown-selected {
display: inline-block;
flex: 1 0 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 15px;
}
}
.dropdown .dropdown-toggle.btn-xs {
height: 22px !important;
line-height: 22px !important;
padding: 0 9px;
}
/*
AutoComplete Field
----------------------------------------------
*/
.dropdown-autocomplete {
position: relative;
padding: 0 !important;
&.btn-xs {height: 22px;}
&.btn-sm {height: 30px;}
&.btn-md {height: 36px;}
&.btn-lg {height: 50px;}
}
.dropdown-autocomplete--input {
position: absolute;
width: 100%;
height: 100%;
outline: none;
background-color: transparent;
border: 0;
color: $g20-white;
padding: 0;
font-weight: 500;
.btn-xs & {padding: 0 18px 0 9px; font-size: 12px;}
.btn-sm & {padding: 0 18px 0 9px; font-size: 13px;}
.btn-md & {padding: 0 34px 0 17px; font-size: 14px;}
.btn-lg & {padding: 0 48px 0 24px; font-size: 18px;}
&::-webkit-input-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&::-moz-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:-ms-input-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:-moz-placeholder { color: rgba(255,255,255,0.5); font-weight: 500 !important; }
&:focus {
color: $g20-white;
}
}
.dropdown-empty {
padding: 7px 9px;
font-size: 13px;
color: rgba(255,255,255,0.4);
font-weight: 500;
line-height: 15px;
@include no-user-select();
}
/*
Dropdown Menu
----------------------------------------------
*/
.dropdown-menu {
width: $dropdown-menu-default-width;
min-width: initial;
margin: 0;
padding: 0;
overflow: hidden;
max-height: $dropdown-menu-max-height;
@include gradient-h($c-ocean, $c-pool);
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.8);
li.dropdown-item {
position: relative;
width: 100%;
&.active {
@include gradient-h($c-sapphire, $c-pool);
}
&:hover {
@include gradient-h($c-laser, $c-pool);
}
}
li.dropdown-item > a {
position: relative;
@include no-user-select();
width: 100%;
border-radius: 0 !important;
display: inline-block;
padding: 7px 9px;
font-size: 13px;
line-height: 15px;
font-weight: 500 !important;
color: $c-yeti !important;
background-color: transparent;
transition:
color 0.25s ease;
&:hover {
cursor: pointer;
background-color: transparent;
color: $g20-white !important;
}
&:active,
&:active:focus {
@include gradient-h($c-sapphire, $c-pool);
}
&:focus {
@include gradient-h($c-ocean, $c-pool);
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-pool);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
.dropdown.dropdown-kapacitor .dropdown-toggle {
color: $c-rainforest !important;
&:hover {color: $c-honeydew !important;}
}
.dropdown.dropdown-kapacitor .dropdown-menu,
.rule-builder .dropdown .dropdown-menu {
@include custom-scrollbar($c-rainforest, $c-honeydew);
@include gradient-h($c-pool, $c-rainforest);
li.dropdown-item:hover {
@include gradient-h($c-laser, $c-rainforest);
}
li.dropdown-item > a {
color: $c-mint !important;
&:hover {
color: $g20-white !important;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-rainforest);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
.dropdown.dropdown-chronograf .dropdown-menu {
@include custom-scrollbar($c-comet, $c-potassium);
@include gradient-h($c-ocean, $c-comet);
li.dropdown-item:hover {
@include gradient-h($c-laser, $c-comet);
}
li.dropdown-item > a {
color: $c-twilight !important;
&:hover {
color: $g20-white !important;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-comet);
}
> a {
background: none;
background-color: transparent;
color: $g20-white;
}
}
}
/*
Dropdown Menu (only js highlighting, works with autocomplete feature)
----------------------------------------------
*/
.dropdown-menu.dropdown-menu--no-highlight {
li.dropdown-item {
&:hover {
background: none;
background-color: transparent;
}
}
li.dropdown-item.highlight {
&, &:hover {
@include gradient-h($c-laser, $c-pool);
}
}
}
/*
Dropdown Header
----------------------------------------------
*/
.dropdown-header {
height: 32px;
line-height: 30px;
padding: 0 9px;
white-space: nowrap;
font-size: 14px !important;
font-weight: 900;
color: $c-neutrino !important;
text-transform: none !important;
border-bottom: 2px solid $c-pool;
background-color: $c-ocean;
&:hover {
background-image: none !important;
background-color: $c-ocean !important;
cursor: default;
}
}
/*
Dropdown Actions
----------------------------------------------
*/
.dropdown-item__actions {
z-index: 2;
position: absolute;
top: 0;
right: 3px;
height: 100%;
width: auto;
display: flex;
align-items: center;
justify-content: flex-end;
}
.dropdown-item__action {
padding: 0;
margin: 0;
width: 24px;
height: 24px;
border-radius: 2px;
background-color: transparent;
border: none !important;
font-size: 13px;
transition:
text-shadow 0.25s ease,
color 0.25s ease;
color: $c-sapphire;
&[data-target="#deleteExplorerModal"] .icon {
position: relative;
right: -1px;
}
&:hover {
color: $g20-white;
text-shadow:
0 0 2px $c-hydrogen,
0 0 3px $c-laser,
0 0 6px $c-ocean;
background-color: transparent;
}
}
/*
Group By Time Dropdown
----------------------------------------------
*/
.group-by-time-dropdown {
.dropdown-toggle {
width: 120px;
height: 28px !important;
line-height: 28px !important;
font-size: 12px;
text-transform: none;
border-radius: $radius;
display: block;
}
.dropdown-menu > li.dropdown-item > a {display: block;}
}
.data-explorer .group-by-time-dropdown .dropdown-toggle {
font-weight: 600;
background-color: $g2-kevlar;
line-height: 24px !important;
border: 2px solid $g6-smoke;
transition:
border-color 0.25s ease,
color 0.25s ease,
box-shadow 0.25s ease;
color: $g10-wolf;
&:hover {
border-color: $g7-graphite;
color: $g18-cloud;
}
}
.data-explorer .group-by-time-dropdown.open .dropdown-toggle {
background-color: $g2-kevlar !important;
border-color: $c-pool !important;
box-shadow: 0 0 6px 0px $c-pool !important
}

View File

@ -138,22 +138,24 @@
position: absolute;
width: 100%;
height: 100%;
font-size: 60px;
font-weight: 300;
color: $c-pool;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
text-align: center;
@include no-user-select();
&.graph-single-stat {
top: 0;
}
}
.single-stat--value {
position: absolute;
top: calc(50% - 15px);
left: 50%;
transform: translate(-50%,-50%);
font-size: 54px;
line-height: 54px;
font-weight: 300;
color: $c-pool;
&.graph-single-stat {
position: absolute;
width: 100%;
top: 0;
pointer-events: none;
&.single-stat--small {
font-size: 38px;
line-height: 38px;
}
}

View File

@ -1,52 +0,0 @@
/*
Styles for Flash Messages
----------------------------------------------
*/
.flash-messages {
position: fixed;
left: 50%;
transform: translateX(-50%);
width: 570px;
top: 36px;
z-index: 9999;
.alert {
font-size: 16px;
font-weight: 500;
border-width: 0;
color: $g20-white;
&.alert-success {
background-color: $c-rainforest;
}
&.alert-primary {
background-color: $c-pool;
}
&.alert-warning {
background-color: $c-comet;
}
&.alert-danger {
background-color: $c-dreamsicle;
}
button.close {
position: absolute;
top: 50%;
right: 13px;
transform: translateY(-50%);
font-size: 16px;
text-shadow: none;
opacity: 0.6;
transition: opacity 0.25s ease;
.icon {
color: $g20-white;
text-shadow: none;
}
&:hover {
opacity: 1;
}
}
}
}

View File

@ -1,159 +0,0 @@
$ms-normal-left-padding: 9px;
$ms-item-height: 26px;
$ms-checkbox-size: 14px;
$ms-checkbox-dot-size: 6px;
$ms-checkbox-bg: $c-sapphire;
$ms-checkbox-bg-hover: $c-ocean;
$ms-checkbox-dot: $g20-white;
.multi-select-dropdown {
.multi-select-dropdown__item > a {
color: $c-neutrino !important;
height: $ms-item-height;
line-height: $ms-item-height;
position: relative;
padding-top: 0;
padding-bottom: 0;
padding-right: $ms-normal-left-padding;
padding-left: ($ms-normal-left-padding + $ms-checkbox-size + ($ms-normal-left-padding - 2px));
&,
&:focus,
&:active,
&:active:focus {
background: none !important;
&:hover {
background: $c-pool;
background: -moz-linear-gradient(left, $c-pool 0%, $c-pool 100%) !important;
background: -webkit-linear-gradient(left, $c-pool 0%,$c-pool 100%) !important;
background: linear-gradient(to right, $c-pool 0%,$c-pool 100%) !important;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$c-pool', endColorstr='$c-pool',GradientType=1 ) !important;
}
}
/* Shared Checkbox Styles */
&:before,
&:after {
content: '';
position: absolute;
display: block;
top: 50%;
}
/* Before = Checkbox */
&:before {
width: $ms-checkbox-size;
height: $ms-checkbox-size;
border-radius: $radius-small;
background-color: $ms-checkbox-bg;
left: $ms-normal-left-padding;
transform: translateY(-50%);
}
/* After = Dot */
&:after {
width: $ms-checkbox-dot-size;
height: $ms-checkbox-dot-size;
background-color: $ms-checkbox-dot;
border-radius: 50%;
transform: translate(-50%,-50%) scale(2,2);
opacity: 0;
left: ($ms-normal-left-padding + ($ms-checkbox-size / 2));
transition:
opacity 0.25s ease,
transform 0.25s ease;
}
/* Hover State */
&:hover {
color: $g20-white !important;
}
}
.dropdown-toggle {
width: 110px;
&.btn-xs {
height: 22px;
line-height: 22px;
padding-left: 0;
padding-right: 0;
}
}
&__apply {
margin: 0;
transition:
opacity 0.25s ease;
opacity: 0;
@include gradient-h($c-ocean, $c-pool);
border-radius: 3px 3px 0 0;
padding: 9px;
& + .dropdown-menu {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
.dropdown-menu {
opacity: 0;
display: block !important;
transition: opacity 0.25s ease;
}
.dropdown-options {
width: 100%;
position: absolute;
top: 100%;
left: 0;
visibility: hidden;
transition-property: all;
}
/* Styling Active State of items */
.dropdown-menu > li.active > a {
outline: none;
&,
&:focus,
&:active,
&:active:focus {
color: $g20-white !important;
background: $c-sapphire;
background: -moz-linear-gradient(left, $c-sapphire 0%, $c-pool 100%) !important;
background: -webkit-linear-gradient(left, $c-sapphire 0%,$c-pool 100%) !important;
background: linear-gradient(to right, $c-sapphire 0%,$c-pool 100%) !important;
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$c-sapphire', endColorstr='$c-pool',GradientType=1 ) !important;
}
}
}
/* Checked State */
.multi-select-dropdown li.multi-select-dropdown__item.active > a {
&,
&:focus,
&:active,
&:active:focus {
background: none !important;
}
color: $g20-white !important;
&:after {
transform: translate(-50%,-50%) scale(1,1);
opacity: 1;
}
}
/* Open State */
.multi-select-dropdown.open {
.dropdown-options {
visibility: visible;
z-index: 9999;
}
.dropdown-menu,
.multi-select-dropdown__apply {
opacity: 1;
}
}
.multi-select-dropdown__label {
top: 50%;
transform: translateY(-50%);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding-right: 10px;
position: absolute;
width: calc(100% - #{($ms-normal-left-padding * 2)});
left: $ms-normal-left-padding;
}

View File

@ -2,12 +2,14 @@
.dropdown-toggle {
height: 38px;
padding-left: 0;
padding-right: (11px + 12px); // caret width + offset
padding-top: 0;
padding-bottom: 0;
min-width: 50px;
width: auto;
border: 0;
background-color: transparent;
text-transform: none;
padding-right: (11px + 12px); // caret width + offset
font-size: $page-header-size;
font-weight: $page-header-weight;
transition: color 0.25s ease;
@ -39,4 +41,4 @@
@include no-user-select();
}
}
}
}

View File

@ -27,8 +27,6 @@
font-weight: $page-header-weight;
padding: 0;
flex: 1;
position: relative;
top: -1px;
&:focus {
color: $c-pool;

View File

@ -114,9 +114,9 @@
transition: color 0.25s ease;
color: $g9-mountain;
}
& > input {
& > input.form-control {
order: 2;
padding: 0 8px 0 24px;
padding-left: 24px;
border-color: $g6-smoke !important;
&:hover {border-color: $g7-graphite !important;}
@ -172,13 +172,6 @@
.query-builder--list-item {padding: 0;}
.query-builder--filter {margin-bottom: 4px;}
}
/* Dropdowns inside query builder list items are forced to "xs" size */
.query-builder--list-item .dropdown-toggle {
height: 22px !important;
line-height: 22px !important;
font-size: 12px !important;
width: 110px !important;
}
/* Toggle for grouping by tags in tags list */
.group-by-tag {
visibility: hidden;
@ -198,9 +191,6 @@
.query-builder--list-item.active .group-by-tag {
visibility: visible;
}
.query-builder--db-dropdown {
display: inline-block;
}
}

View File

@ -88,14 +88,6 @@
.dropdown.query-editor--templates {
margin: 0 4px 0 0 ;
div.dropdown-toggle.btn.btn-sm {
width: $query-editor--templates-width;
padding: 0 9px !important;
height: $query-editor--templates-height !important;
line-height: $query-editor--templates-height !important;
font-size: 12px;
border-radius: $radius-small;
}
.dropdown-menu {
left: initial;
right: 0;

View File

@ -8,7 +8,7 @@
.query-maker {
height: 100%;
margin: 0 $explorer-page-padding;
margin: 0 $page-wrapper-padding;
display: flex;
flex-direction: column;
align-items: stretch;
@ -90,7 +90,7 @@ $query-editor-tab-active: $g3-castle;
margin: 0 !important;
}
.query-maker--tab {
border-radius: $radius $radius 0 0;
border-radius: $radius $radius 0 0;
height: $query-maker--tabs-height;
margin: 0 2px 0 0;
max-width: $query-maker--tab-width;
@ -195,4 +195,4 @@ $query-editor-tab-active: $g3-castle;
*/
@import 'query-editor';
@import 'query-builder';
@import 'query-maker-responsive';
@import 'query-maker-responsive';

View File

@ -1,7 +1,13 @@
.alert-value-set {
padding: 6px 13px 0;
span a {
margin-left: 10px;
}
.redacted-input {
height: 38px;
align-items: center;
justify-content: space-between;
}
.alert-value-set {
font-weight: 700;
font-size: 13px;
color: $c-rainforest;
@include no-user-select();
.icon {margin-right: 5px;}
}

View File

@ -1,6 +1,9 @@
/*
Stuff for making Tables of Data more readable
Table Styles
----------------------------------------------
Most table styles are located in the Bootstrap
Theme. Styles in here are for specific additions
to tables that bootstrap does not do on its own
*/
// table-custom class allows us to make a table from divs so we can use
@ -43,58 +46,6 @@
}
}
table {
thead th {
color: $g17-whisper !important;
border-width: 1px;
border-color: $g5-pepper !important;
}
tbody td {
font-weight: 500;
color: $g14-chromium !important;
border: 0 !important;
padding: 6px 8px !important;
}
tbody tr:hover {
background-color: $g4-onyx;
td {
color: $g19-ghost !important;
}
}
}
table .monotype {
font-family: $code-font;
letter-spacing: 0px;
font-size: 12px;
font-weight: 500;
color: $g9-mountain;
}
.table-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: $g17-whisper;
&.dot-success {
background-color: $c-rainforest;
}
&.dot-primary {
background-color: $c-pool;
}
&.dot-warning {
background-color: $c-pineapple;
}
&.dot-danger {
background-color: $c-dreamsicle;
}
&.dot-critical {
background-color: $c-fire;
}
}
/*
Sortable Tables
----------------------------------------------
@ -181,7 +132,7 @@ $table-tab-scrollbar-height: 6px;
.table--tab {
font-size: 12px;
font-weight: 600;
@include no-user-select();
@include no-user-select();
height: $table-tab-height;
border-radius: $radius-small $radius-small 0 0;
line-height: $table-tab-height;

View File

@ -36,7 +36,7 @@ $template-control-dropdown-min-width: 146px;
@include no-user-select();
white-space: nowrap;
}
.template-control--manage {
button.btn.template-control--manage {
margin: 7px 8px;
}
.template-control--controls {
@ -64,38 +64,12 @@ $template-control-dropdown-min-width: 146px;
margin: 0;
flex: 1 0 0;
}
.dropdown-toggle {
border-radius: 0 0 $radius-small $radius-small;
width: 100%;
font-size: 12px;
font-family: $code-font;
}
.dropdown-menu {
@include gradient-h($c-star,$c-pool);
li.dropdown-item {
&, &:hover {
background: none;
background-color: transparent;
}
&.active {@include gradient-h($c-amethyst,$c-pool);}
}
li.dropdown-item > a {
&, &:focus {background: none;}
&:active, &:active:focus {@include gradient-h($c-amethyst,$c-pool);}
font-size: 12px;
font-family: $code-font;
}
li.dropdown-item.highlight {
&, &:hover {@include gradient-h($c-comet,$c-pool);}
> a {
color: $g20-white;
background: none;
background-color: transparent;
}
}
}
}
.template-control--label {
@include no-user-select();

View File

@ -191,11 +191,6 @@ $tvmp-table-gutter: 8px;
.btn-edit {
order: 1;
width: 30px;
text-align: center;
padding-left: 0 !important;
padding-right: 0 !important;
> span.icon {margin: 0 !important;}
}
> .btn {margin-left: $tvmp-table-gutter;}
@ -204,17 +199,9 @@ $tvmp-table-gutter: 8px;
/* Janky, but doing this quick & dirty for now */
.btn-danger {
order: 2;
height: 30px !important;
line-height: 30px !important;
padding: 0 9px !important;
font-size: 13px;
}
.confirm-buttons > .btn {
height: 30px !important;
width: 30px !important;
margin-left: $tvmp-table-gutter !important;
font-size: 13px;
padding: 0 !important;
}
/* Hide the edit button when confirming a delete */
.confirm-buttons + .btn-edit {

View File

@ -0,0 +1,12 @@
/*
Styles for Flash Messages
----------------------------------------------
*/
.flash-messages {
position: fixed;
left: 50%;
transform: translateX(-50%);
width: 570px;
top: 36px;
z-index: 9999;
}

View File

@ -35,10 +35,6 @@ $page-header-weight: 400 !important;
.page-header__right {
display: flex;
align-items: center;
> *:only-child {
margin: 0;
}
}
.page-header__left {
justify-content: flex-start;
@ -49,10 +45,15 @@ $page-header-weight: 400 !important;
.page-header__right {
justify-content: flex-end;
> * {
margin: 0 0 0 4px;
margin: 0 0 0 4px !important;
&:only-child {
margin-right: 0 !important;
}
}
}
.page-header__title {
letter-spacing: 0;
text-transform: none;
font-size: $page-header-size;
font-weight: $page-header-weight;
@ -94,4 +95,4 @@ $page-header-weight: 400 !important;
&:hover:after {
background-color: $g18-cloud;
}
}
}

View File

@ -3,9 +3,9 @@
----------------------------------------------
*/
$sidebar-width: 60px;
$sidebar-menu-gutter: 30px;
$sidebar-menu-indent: 13px;
$sidebar-radius: 4px;
$sidebar-hover: $g8-storm;
$sidebar-item-hover: $g7-graphite;
@ -21,38 +21,6 @@ $sidebar-active-accent: $c-laser;
$sidebar-logo-bg: $g17-whisper;
$sidebar-logo-color: $g8-storm;
// Rename button
.js-open-rename-cluster-modal {
display: inline-block;
height: 20px;
width: 20px;
border: none;
background-color: $g8-storm;
vertical-align: middle;
border-radius: 3px;
margin-left: 11px;
margin-right: -25px;
position: relative;
font-size: 13px;
color: $g20-white;
opacity: 0.4;
transition:
background-color 0.25s ease,
opacity 0.25s ease;
.icon.pencil {
position: absolute;
top: 3px;
left: 4px;
}
&:hover {
background-color: $g9-mountain;
cursor: pointer;
}
}
// Sidebar styles
.sidebar {
display: flex;
@ -176,7 +144,7 @@ $sidebar-logo-color: $g8-storm;
}
&__menu {
border-radius: 0 $sidebar-radius $sidebar-radius 0;
border-radius: 0 $radius $radius 0;
background: $sidebar-hover;
opacity: 0;
transition: opacity 0.25s ease;
@ -237,12 +205,12 @@ $sidebar-logo-color: $g8-storm;
// Rounding top outside corner to match container
&:first-child {
border-top-right-radius: $sidebar-radius;
border-top-right-radius: $radius;
}
// Rounding bottom outside corner of match container
&:last-child {
border-bottom-right-radius: $sidebar-radius;
border-bottom-right-radius: $radius;
}
// Used for sub-navigation
@ -314,5 +282,4 @@ $sidebar-logo-color: $g8-storm;
font-weight: 400;
padding: 0px $sidebar-menu-gutter;
}
}
}

View File

@ -109,6 +109,9 @@ $scrollbar-offset: 3px;
-webkit-user-select: none !important;
-ms-user-select: none !important;
-o-user-select: none !important;
&, &:hover {
cursor: default;
}
}
.no-user-select {
user-select: none !important;
@ -116,4 +119,7 @@ $scrollbar-offset: 3px;
-webkit-user-select: none !important;
-ms-user-select: none !important;
-o-user-select: none !important;
&, &:hover {
cursor: default;
}
}

View File

@ -1,14 +1,5 @@
$radius: 4px;
$radius-small: 3px;
// Sidebar
$sidebar-width: 60px;
$page-wrapper-padding: 58px;
$page-wrapper-padding: 60px;
$page-wrapper-max-width: 1300px;
$current-user-height: 56px;
$cluster-switcher-height: 56px;
$chronograf-page-header-height: 60px;
$sidebar-tier1-height: 56px;
// Data Explorer
$explorer-page-padding: $page-wrapper-padding;

View File

@ -12,22 +12,7 @@
*/
.admin-tabs {
padding-right: 0;
& + div {
padding-left: 0;
.panel {
border-top-left-radius: 0;
}
.panel-body {
min-height: 300px;
}
.panel-title {
font-size: 17px;
font-weight: 400 !important;
color: $g12-forge;
}
}
& + div {padding-left: 0;}
}
.admin-tabs .btn-group {
margin: 0;
@ -64,113 +49,90 @@
}
}
}
.admin-tabs--content {
.panel {border-top-left-radius: 0;}
.panel-heading {height: 60px;}
.panel-title {
font-size: 17px;
font-weight: 400 !important;
color: $g12-forge;
padding: 0;
}
.panel-body {min-height: 300px;}
.panel-heading + .panel-body {padding-top: 0;}
}
/*
Admin Table
----------------------------------------------
*/
.admin-table {
.multi-select-dropdown {
width: 100%;
min-width: 150px;
}
.admin-table--kill-button {
width: 70px;
}
.admin-table--hidden {
visibility: hidden;
}
.dropdown-toggle {
background-color: transparent;
font-size: 14px;
font-weight: 500;
color: $g14-chromium;
width: 100%;
transition: none !important;
.admin-table .dropdown-toggle {
background-color: transparent;
font-weight: 600;
color: $g14-chromium;
transition: none !important;
.caret {opacity: 0;}
.multi-select-dropdown__label {left: 0;}
}
.open .dropdown-toggle .multi-select-dropdown__label {left: 9px;}
tbody tr:hover {
.admin-table--hidden {
visibility: visible;
}
.dropdown-toggle {
color: $g20-white !important;
background-color: $c-pool;
font-weight: 600;
.caret {opacity: 0;}
}
.admin-table--multi-select-empty .dropdown-toggle {
color: $g8-storm;
}
.admin-table tbody tr:hover .dropdown-toggle {
color: $g20-white !important;
background-color: $c-pool;
.caret {opacity: 1;}
.multi-select-dropdown__label {left: 9px;}
.caret {opacity: 1;}
&:hover {
transition: background-color 0.25s ease;
background-color: $c-laser;
}
}
}
.multi-select-dropdown.open .dropdown-toggle {
color: $g20-white !important;
background-color: $c-laser !important;
font-weight: 600;
.multi-select-dropdown__label {left: 9x !important;}
&:hover {
transition: background-color 0.25s ease;
background-color: $c-laser;
}
}
.admin-table--edit-row {
background-color: $g4-onyx;
table > tbody > tr > td.admin-table--left-offset,
table > thead > tr > th.admin-table--left-offset {padding-left: 15px;}
table > tbody > tr.admin-table--edit-row,
table > tbody > tr.admin-table--edit-row:hover,
table.table-highlight > tbody > tr.admin-table--edit-row,
table.table-highlight > tbody > tr.admin-table--edit-row:hover {
background-color: $g5-pepper;
}
.admin-table--edit-cell {
width: 100%;
margin: 0 !important;
display: flex !important;
justify-content: space-between;
.admin-table--change-pw {
display: flex;
flex-wrap: nowrap;
> input {
height: 30px;
padding: 0 9px;
flex-grow: 1;
margin: 0 2px;
min-width: 110px;
&:first-child {margin-left: 0;}
&:last-child {margin-right: 0;}
}
}
.admin-table--delete-cell {
margin: 0 !important;
display: flex !important;
justify-content: space-between;
> input {
height: 22px;
padding: 0 6px;
flex-grow: 1;
.form-control {
margin: 0 4px 0 0;
min-width: 110px;
font-size: 12px;
flex: 1 0 0;
}
}
pre.admin-table--query {
width: 100%;
padding: 0;
border-radius: 0;
background-color: transparent;
margin-bottom: 0;
}
.admin-table--delete-db {
display: flex;
align-items: center;
& + .confirm-buttons .btn {
display: block !important;
> .form-control {
flex: 1 0 0;
margin-right: 4px;
}
}
/*
Database Manager
----------------------------------------------
*/
.db-manager {
margin-bottom: 8px;
.db-manager-header--actions {
display: none;
}
&:last-child {
margin-bottom: 0;
}
&:hover .db-manager-header--actions {
display: block;
}
&:last-child {margin-bottom: 0;}
.db-manager-header--actions {display: none;}
&:hover .db-manager-header--actions {display: block;}
}
.db-manager-header {
padding: 0 11px;
@ -184,21 +146,18 @@
h4 {
margin: 0px;
color: $c-potassium;
font-size: 17px;
font-size: 16px;
font-family: $code-font;
padding-left: 6px;
width: auto;
}
}
.db-manager-header--edit {
justify-content: flex-start;
.form-control {
height: 22px;
padding: 0 6px;
margin: 0 4px 0 0;
min-width: 110px;
font-size: 12px;
width: 50%;
margin: 0 8px 0 0;
flex: 1 0 0;
}
}
@ -206,20 +165,6 @@
background-color: $g4-onyx;
padding: 9px 11px;
border-radius: 0 0 $radius-small $radius-small;
}
.admin-change-pw {
float: right;
display: flex !important;
flex-wrap: nowrap;
.form-control {
height: 22px;
padding: 0 6px;
margin: 0 4px 0 0;
min-width: 110px;
font-size: 12px;
width: 120px;
}
.table-highlight > tbody > tr:hover {background-color: $g5-pepper;}
}

View File

@ -145,13 +145,13 @@ $dash-graph-options-arrow: 8px;
height: $dash-graph-heading;
line-height: $dash-graph-heading;
width: calc(100% - 50px);
margin-left: 16px;
padding-left: 16px;
transition:
color 0.25s ease,
background-color 0.25s ease,
border-color 0.25s ease;
}
.dash-graph--name-edit {
input.form-control.dash-graph--name-edit {
margin-left: 8px;
padding: 0 6px;
width: calc(100% - 42px);
@ -166,6 +166,7 @@ $dash-graph-options-arrow: 8px;
z-index: 11;
right: 0px;
top: 0px;
text-align: center;
> .btn {
background-color: transparent !important;
@ -197,6 +198,7 @@ $dash-graph-options-arrow: 8px;
display: block;
list-style: none;
padding: 0;
margin: 0;
width: 90px;
visibility: hidden;
transition-property: all;
@ -208,6 +210,8 @@ $dash-graph-options-arrow: 8px;
line-height: 28px;
background-color: $g5-pepper;
padding: 0 11px;
margin: 0;
text-align: left;
color: $g15-platinum;
opacity: 0;
transition:

View File

@ -1,47 +1,230 @@
/*
Kapacitor Rule Builder
----------------------------------------------
---------------------------------------------------------------------------
*/
$kapacitor-page-padding: ($chronograf-page-header-height / 2);
$kapacitor-page-gutter: 46px;
$kapacitor-dot-size: 18px;
$kapacitor-line-width: 3px;
$metric-selector-height: 156px;
$kap-padding-sm: 8px;
$kap-padding-md: 13px;
$kap-padding-lg: 24px;
$kap-radius-lg: 5px;
$kap-input-height: 30px;
$rule-builder--accent-color: $c-rainforest;
$rule-builder--left-gutter: 46px;
$rule-builder--section-gap: ($chronograf-page-header-height / 2);
$rule-builder--section-bg: $g3-castle;
$rule-builder--section-border: $g2-kevlar;
$rule-builder--dot: 18px;
$rule-builder--accent-line-width: 3px;
$rule-builder--accent-line-color: $g5-pepper;
$rule-builder--font-size: 13px;
$rule-builder--query-builder-height: 240px;
$kapacitor-graphic-color: $g3-castle;
$kapacitor-divider-color: $g2-kevlar;
$kapacitor-accent: $c-rainforest;
$kap-line-color: $g5-pepper;
$kap-dot-color: $c-rainforest;
$kapacitor-font-sm: 13px;
$rule-builder--padding-sm: 8px;
$rule-builder--padding-md: 13px;
$rule-builder--padding-lg: 24px;
$rule-builder--radius-lg: 5px;
.rule-builder {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
}
.rule-builder p {
width: auto;
margin: 0 ($rule-builder--padding-sm - 2px);
font-weight: 600;
color: $g15-platinum;
@include no-user-select();
&:first-child {margin-left: 0;}
}
/*
Generic Rule Section styles
-----------------------------------------------------------------------------
*/
.rule-section--heading,
.rule-section--body {
padding-left: $rule-builder--left-gutter;
position: relative;
&:before,
&:after {
content: '';
display: block;
position: absolute;
}
// Vertical Line
&:before {
transform: translateX(-50%);
width: $rule-builder--accent-line-width;
height: 100%;
background-color: $rule-builder--accent-line-color;
top: 0;
left: ($rule-builder--dot/2);
}
}
.rule-section--heading {
margin: 0;
padding: $rule-builder--section-gap 0 $rule-builder--padding-md $rule-builder--left-gutter;
font-size: $page-header-size;
font-weight: $page-header-weight;
color: $g12-forge;
@include no-user-select();
// Dot
&:after {
transform: translateX(-50%);
width: $rule-builder--dot;
height: $rule-builder--dot;
background-color: $rule-builder--accent-color;
border: 6px solid $rule-builder--accent-line-color;
border-radius: 50%;
top: ($rule-builder--section-gap + 3px);
left: ($rule-builder--dot / 2);
}
}
// Override appearance of lines and dots for first section
.rule-section:first-of-type {
.rule-section--heading {
padding-top: 0;
}
.rule-section--heading:before {
top: 5px;
height: calc(100% - 5px);
}
.rule-section--heading:after {
top: 3px;
}
}
// Override appearance of lines and dots for last section
.rule-section:last-of-type {
.rule-section--heading:before {
top: 0;
height: ($rule-builder--section-gap + 3px + 3px);
}
.rule-section--body:before {
display: none;
}
}
// Generic re-usable classes for rule builder sections
.rule-section--border-top {
border-top: 2px solid $rule-builder--section-border;
}
.rule-section--border-bottom {
border-bottom: 2px solid $rule-builder--section-border;
}
.rule-section--row {
display: flex;
align-items: center;
flex-wrap: wrap;
background-color: $rule-builder--section-bg;
padding: $rule-builder--padding-sm $rule-builder--padding-lg;
}
.rule-section--row-first {
border-top-left-radius: $rule-builder--radius-lg;
border-top-right-radius: $rule-builder--radius-lg;
}
.rule-section--row-last {
border-bottom-left-radius: $rule-builder--radius-lg;
border-bottom-right-radius: $rule-builder--radius-lg;
}
.rule-section--row .form-control + .form-control,
.rule-section--row .dropdown + .form-control {
margin-left: $rule-builder--padding-sm - 2px;
}
/*
Section 1 - Select a Time Series
-----------------------------------------------------------------------------
Overrides are scoped to the page class .rule-builder
*/
.rule-builder {
/* Query Preview */
pre {
font-size: $rule-builder--font-size;
background-color: $rule-builder--section-bg;
border-radius: $rule-builder--radius-lg $rule-builder--radius-lg 0 0;
margin: 0;
padding: $rule-builder--padding-md $rule-builder--padding-lg;
}
pre code {
line-height: ($rule-builder--font-size + 3px);
white-space: pre-wrap;
&.metric-placeholder {
color: $g8-storm;
font-style: italic;
}
}
.query-builder {
height: $rule-builder--query-builder-height;
overflow: visible;
}
.query-builder--column {
margin-right: 2px;
&:last-child {margin-right: 0;}
}
.query-builder--column:nth-child(1) .query-builder--list {
border-bottom-left-radius: $rule-builder--radius-lg;
}
.query-builder--column:nth-child(4) .query-builder--list,
.query-builder--column:nth-child(4) .query-builder--list-empty {
border-bottom-right-radius: $rule-builder--radius-lg;
}
.query-builder--heading {
background-color: $rule-builder--section-bg;
margin-bottom: 2px;
}
.query-builder--list {
@include custom-scrollbar($rule-builder--section-bg, $rule-builder--accent-color);
}
.group-by-tag.active {
background-color: $c-rainforest !important;
&:hover {background-color: $c-honeydew !important;}
}
.query-builder--list-item .query-builder--checkbox:after {
background-color: $c-rainforest;
}
.query-builder--filter {
input.form-control {
color: $c-rainforest !important;
&:focus {
color: $g20-white !important;
box-shadow: 0 0 6px 0px $c-rainforest !important;
border-color: $c-rainforest !important;
}
&:focus + span.icon {
color: $c-rainforest !important;
}
}
}
}
/*
Sectiom 2 - Rule Conditions
-----------------------------------------------------------------------------
*/
.rule-builder--metric {
height: 30px;
line-height: 30px;
border-radius: 3px;
background-color: $g5-pepper;
color: $rule-builder--accent-color;
font-size: 13px;
font-weight: 700;
padding: 0 9px;
@include no-user-select();
}
.rule-builder--graph {
margin-left: $kapacitor-page-gutter;
width: calc(100% - #{$kapacitor-page-gutter});
background-color: $kapacitor-graphic-color;
border-radius: 0 0 $kap-radius-lg $kap-radius-lg;
padding: 0 $kap-padding-sm;
height: (300px + ($kap-padding-sm * 2));
margin-left: $rule-builder--left-gutter;
width: calc(100% - #{$rule-builder--left-gutter});
background-color: $rule-builder--section-bg;
border-radius: 0 0 $rule-builder--radius-lg $rule-builder--radius-lg;
padding: 0 $rule-builder--padding-sm;
height: (300px + ($rule-builder--padding-sm * 2));
position: relative;
& > div {
position: absolute;
top: 0;
left: $kap-padding-sm;
width: calc(100% - #{($kap-padding-sm * 2)});
left: $rule-builder--padding-sm;
width: calc(100% - #{($rule-builder--padding-sm * 2)});
height: 100%;
& > div {
@ -57,11 +240,11 @@ $kapacitor-font-sm: 13px;
display: block;
position: absolute;
transform: translateX(-50%);
width: $kapacitor-line-width;
width: $rule-builder--accent-line-width;
height: 100%;
background-color: $kap-line-color;
background-color: $rule-builder--accent-line-color;
top: 0;
left: (($kapacitor-dot-size / 2) - $kapacitor-page-gutter);
left: (($rule-builder--dot / 2) - $rule-builder--left-gutter);
}
.container--dygraph-legend {
background-color: $g5-pepper;
@ -71,7 +254,7 @@ $kapacitor-font-sm: 13px;
}
}
}
.rule-preview--graph-empty {
.rule-builder--graph-empty {
position: absolute;
top: 0;
left: 0;
@ -96,378 +279,42 @@ $kapacitor-font-sm: 13px;
}
}
.rule-section-heading {
margin: 0;
padding: $kapacitor-page-padding 0 $kap-padding-md $kapacitor-page-gutter;
font-size: $page-header-size;
font-weight: $page-header-weight;
color: $g12-forge;
position: relative;
@include no-user-select();
/*
Section 3 - Rule Message
-----------------------------------------------------------------------------
*/
&:before,
&:after {
content: '';
display: block;
position: absolute;
transform: translateX(-50%);
}
// Vertical Line
&:before {
width: $kapacitor-line-width;
height: 100%;
background-color: $kap-line-color;
top: 0;
left: ($kapacitor-dot-size/2);
}
// Dot
&:after {
width: $kapacitor-dot-size;
height: $kapacitor-dot-size;
background-color: $kap-dot-color;
border: 6px solid $kap-line-color;
border-radius: 50%;
top: ($kapacitor-page-padding + 3px);
left: ($kapacitor-dot-size / 2);
}
}
.rule-section-body {
padding: 0 0 0 $kapacitor-page-gutter;
margin: 0;
position: relative;
// Vertical Line
&:before {
content: '';
display: block;
position: absolute;
transform: translateX(-50%);
width: $kapacitor-line-width;
height: 100%;
background-color: $kap-line-color;
top: 0;
left: ($kapacitor-dot-size / 2);
}
}
.kapacitor-rule-section {
// Override appearance of lines and dots for first child
&:first-of-type {
.rule-section-heading:before {
top: ($kapacitor-page-padding + 5px);
height: calc(100% - #{$kapacitor-page-padding} - 5px);
}
}
// Override appearance of lines and dots for last child
&:last-of-type {
.rule-section-heading:before {
top: 0;
height: ($kapacitor-page-padding + 3px + 3px);
}
.rule-section-body:before {
display: none;
}
}
}
.kapacitor-metric-selector {
/* Query Preview */
pre {
font-size: $kapacitor-font-sm;
background-color: $kapacitor-graphic-color;
color: $c-pool;
border-radius: $kap-radius-lg $kap-radius-lg 0 0;
border: 0;
margin: 0;
padding: $kap-padding-md $kap-padding-lg;
}
pre code {
line-height: ($kapacitor-font-sm + 3px);
white-space: pre-wrap;
&.kapacitor-metric-placeholder {
color: $g8-storm;
font-style: italic;
}
}
.query-builder {
height: 240px;
margin-top: 2px;
overflow: visible;
}
.query-builder--column {
margin-right: 2px;
&:last-child {margin-right: 0;}
}
.query-builder--column:nth-child(1) .query-builder--list {
border-bottom-left-radius: $kap-radius-lg;
}
.query-builder--column:nth-child(4) .query-builder--list,
.query-builder--column:nth-child(4) .query-builder--list-empty {
border-bottom-right-radius: $kap-radius-lg;
}
.query-builder--heading {
background-color: $kapacitor-graphic-color;
margin-bottom: 2px;
}
.query-builder--list {
@include custom-scrollbar($kapacitor-graphic-color, $kapacitor-accent);
}
.group-by-tag.active {
background-color: $c-rainforest !important;
&:hover {background-color: $c-honeydew !important;}
}
.query-builder--list-item .query-builder--checkbox:after {
background-color: $c-rainforest;
}
.query-builder--filter {
input.form-control {
color: $c-rainforest !important;
&:focus {
color: $g20-white !important;
box-shadow: 0 0 6px 0px $c-rainforest !important;
border-color: $c-rainforest !important;
}
&:focus + span.icon {
color: $c-rainforest !important;
}
}
}
}
.alert-text {
border: 2px solid $g3-castle;
background-color: $kapacitor-graphic-color;
margin: 0;
padding: $kap-padding-sm $kap-padding-lg;
color: $kapacitor-accent;
width: 100%;
textarea.rule-builder--message {
border-color: $rule-builder--section-bg;
background-color: $rule-builder--section-bg;
padding: $rule-builder--padding-sm ($rule-builder--padding-lg - 2px);
height: 100px;
min-width: 100%;
max-width: 100%;
display: block;
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-weight: 600;
font-size: $kapacitor-font-sm;
line-height: 17px;
transition:
color 0.25s ease,
border-color 0.25s ease;
border-radius: 0;
@include custom-scrollbar($rule-builder--section-bg,$rule-builder--accent-color);
@include custom-scrollbar($kapacitor-graphic-color,$kapacitor-accent);
&:hover {border-color: $g4-onyx;}
&:focus {background-color: $rule-builder--section-bg;}
}
.rule-builder--message-template {
height: 30px;
line-height: 30px;
padding: 0 ($rule-builder--padding-sm - 2px);
margin: 2px;
transition: color 0.25s ease;
@include no-user-select();
&:hover {
border-color: $g4-onyx;
}
&:focus {
outline: none;
color: $g20-white;
border-color: $kapacitor-accent;
}
&::-webkit-input-placeholder { color: $g9-mountain; }
&::-moz-placeholder { color: $g9-mountain; }
&:-ms-input-placeholder { color: $g9-mountain; }
&:-moz-placeholder { color: $g9-mountain; }
&::selection {
background-color: $c-rainforest !important;
color: $g20-white !important;
}
&::-moz-selection {
background-color: $c-rainforest !important;
color: $g20-white !important;
color: $rule-builder--accent-color;
cursor: pointer;
}
}
.alert-message--endpoint {
width: auto;
border-radius: $kap-radius-lg $kap-radius-lg 0 0;
border-bottom: 2px solid $kapacitor-divider-color;
& > div {
display: flex;
align-items: center;
}
p {
margin-right: $kap-padding-sm !important;
}
}
.alert-message--formatting {
padding: ($kap-padding-sm - 2px) $kap-padding-lg;
background-color: $kapacitor-graphic-color;
display: flex;
align-items: center;
flex-wrap: wrap;
border-top: 2px solid $kapacitor-divider-color;
border-radius: 0 0 $kap-radius-lg $kap-radius-lg;
> p {
margin: 0 ($kap-padding-sm - 2px) 0 0;
font-weight: 600;
display: inline-block;
color: $g15-platinum;
@include no-user-select();
}
> code {
background-color: $g2-kevlar;
height: $kap-input-height;
line-height: $kap-input-height;
padding: 0 ($kap-padding-sm - 2px);
border: 0;
border-radius: 3px;
display: inline-block;
margin: 2px;
color: $c-pool;
font-size: ($kapacitor-font-sm - 2px);
font-weight: 600;
transition: color 0.25s ease;
@include no-user-select();
&:hover {
color: $c-rainforest;
cursor: pointer;
}
}
}
.alert-message--config {
width: 100%;
display: flex;
align-items: center;
flex-wrap: nowrap;
border-bottom: 2px solid $kapacitor-divider-color;
& > p {
margin-right: ($kap-padding-sm - 2px) !important;
}
}
.alert-message--email-body {
border-bottom: 2px solid $kapacitor-divider-color;
}
.rule-section--item {
background-color: $kapacitor-graphic-color;
padding: $kap-padding-sm $kap-padding-lg;
overflow: visible;
p {
margin: 0;
font-weight: 600;
display: inline-block;
color: $g15-platinum;
@include no-user-select();
}
&.top {
border-top-left-radius: $kap-radius-lg;
border-top-right-radius: $kap-radius-lg;
}
&.bottom {
border-bottom-left-radius: $kap-radius-lg;
border-bottom-right-radius: $kap-radius-lg;
}
}
.kapacitor-values-tabs,
.kapacitor-alert-message,
.value-selector {
background-color: $kapacitor-graphic-color;
padding: ($kap-padding-sm - 2px) $kap-padding-lg;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
> * {
display: inline-block;
margin: 2px;
&:first-child {
margin-left: 0;
}
}
> p {
@include no-user-select();
white-space: nowrap;
font-weight: 600;
color: $g15-platinum;
margin-left: ($kap-padding-sm / 2);
margin-right: ($kap-padding-sm / 2);
}
> span {
@include no-user-select();
color: $kapacitor-accent;
height: $kap-input-height;
line-height: $kap-input-height;
display: inline-block;
vertical-align: middle;
border-radius: 4px;
background-color: $g5-pepper;
margin: 2px;
padding: 0 $kap-padding-sm;
font-weight: 700;
font-size: $kapacitor-font-sm;
&:hover {
cursor: default;
}
}
}
.kapacitor-values-tabs,
.kapacitor-alert-message, {
border-radius: $kap-radius-lg $kap-radius-lg 0 0;
border-bottom: 2px solid $kapacitor-divider-color;
.tab-group {
padding: 0;
> .btn.tab {
padding: 0 $kap-padding-md;
height: $kap-input-height;
line-height: ($kap-input-height - 4px);
font-size: $kapacitor-font-sm;
font-weight: 700;
background-color: $kapacitor-graphic-color;
border-color: $g5-pepper;
color: $g11-sidewalk;
&:hover {
background-color: $g4-onyx;
color: $g20-white;
}
&.active {
background-color: $g5-pepper;
color: $kapacitor-accent;
}
}
}
}
.rule-builder .form-control--green {
font-weight: 600 !important;
}
input.size-486 {width: 486px;}
input.size-384 {width: 384px;}
input.size-256 {width: 256px;}
input.size-176 {width: 176px;}
input.size-166 {width: 166px;}
input.size-136 {width: 136px;}
input.size-106 {width: 106px;}
input.size-66 {width: 66px;}
input.size-49 {width: 49px;}
.dropdown.size-486 .dropdown-toggle {width: 486px;}
.dropdown.size-384 .dropdown-toggle {width: 384px;}
.dropdown.size-256 .dropdown-toggle {width: 256px;}
.dropdown.size-176 .dropdown-toggle {width: 176px;}
.dropdown.size-166 .dropdown-toggle {width: 166px;}
.dropdown.size-136 .dropdown-toggle {width: 136px;}
.dropdown.size-106 .dropdown-toggle {width: 106px;}
.dropdown.size-66 .dropdown-toggle {width: 66px;}
.dropdown.size-49 .dropdown-toggle {width: 49px;}
/*
Color coding for alerts in Alert History table
-----------------------------------------------------------------------------
*/
.alert-level-ok {
&, &:hover {color: $c-rainforest !important;}
}

View File

@ -39,8 +39,8 @@ $overlay-z: 100;
align-items: center;
justify-content: space-between;
flex: 0 0 $overlay-controls-height;
width: calc(100% - #{($explorer-page-padding * 2)});
left: $explorer-page-padding;
width: calc(100% - #{($page-wrapper-padding * 2)});
left: $page-wrapper-padding;
border: 0;
background-color: $g2-kevlar;
border-radius: $radius $radius 0 0;
@ -54,13 +54,16 @@ $overlay-z: 100;
margin: 0 0 0 5px;
}
p {
width: auto;
font-weight: 600;
color: $g13-mist;
margin: 0;
margin: 0 6px 0 0;
@include no-user-select;
white-space: nowrap;
}
}
.overlay--graph-name {
width: auto;
margin: 0;
font-size: 17px;
font-weight: 400;
@ -78,28 +81,6 @@ $overlay-z: 100;
.overlay-controls .confirm-buttons {
margin-left: 32px;
}
.overlay-controls .confirm-buttons .btn {
height: 30px;
line-height: 30px;
padding: 0 9px;
& > span.icon {
font-size: 16px;
}
}
.overlay-controls .toggle {
.toggle-btn {
background-color: $overlay-controls-bg;
&:hover {
background-color: $g4-onyx;
}
&.active {
background-color: $g5-pepper;
}
}
}
/* Graph editing in Dashboards is a little smaller so the dash can be seen in the background */
.overlay-technology .graph {

File diff suppressed because it is too large Load Diff

View File

@ -1,611 +0,0 @@
/*
Dark Theme Styles
----------------------------------------------
This stylesheet has overrides for the theme, so you are
going to see al ot of !important =(
This is design debt. One day the theme will not have to be overrided
*/
/*
Dark Panel Styles
----------------------------------------------
*/
.row:only-child .panel,
.row:last-child .panel {
margin-bottom: 0;
}
.panel hr {
background-color: $g5-pepper;
}
.panel-title,
.panel-title a {
@include no-user-select();
}
.panel-minimal {
border: 0;
.panel-title {
color: $g12-forge !important;
font-size: 19px;
font-weight: 400 !important;
}
.panel-body {
padding: 30px;
background-color: $g3-castle;
border: 0;
color: $g13-mist;
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
}
}
.panel.panel-info {
background-color: $g3-castle;
border: 0;
.panel-body,
.panel-heading {
background-color: transparent;
}
.panel-body {
padding: 0px 30px 30px 30px;
}
.panel-heading {
padding: 0 30px;
height: 60px;
border: 0px;
.panel-title { color: $g14-chromium;}
}
}
.panel .panel-body table {
margin: 0;
}
table thead th {
@include no-user-select();
}
/*
Dark Buttons
----------------------------------------------
*/
.btn {
border: 0;
transition:
background-color 0.25s ease,
color 0.25s ease,
box-shadow 0.25s ease;
}
.btn.btn-sm {
font-size: 13px;
line-height: 30px !important;
height: 30px !important;
padding: 0 9px !important;
}
a.btn.btn-sm > span.icon,
div.btn.btn-sm > span.icon,
button.btn.btn-sm > span.icon {
font-size: 16px;
margin: 0 6px 0 0 ;
}
.btn.btn-xs > .icon {
position: relative;
top: -1px;
}
.btn.disabled,
.btn[disabled="true"],
.btn[disabled] {
&.btn-success,
&.btn-success:hover,
&.btn-success:focus,
&.btn-success:active,
&.btn-success:hover:active,
&.btn-success:hover:focus,
&.btn-success:focus:active,
&.btn-success:focus:active:hover {
border-color: $c-emerald;
background-color: $c-emerald;
color: $g2-kevlar !important;
}
&.btn-primary,
&.btn-primary:hover,
&.btn-primary:focus,
&.btn-primary:active,
&.btn-primary:hover:active,
&.btn-primary:hover:focus,
&.btn-primary:focus:active,
&.btn-primary:focus:active:hover {
border-color: $c-sapphire;
background-color: $c-sapphire;
color: $g2-kevlar !important;
}
&.btn-info,
&.btn-info:hover,
&.btn-info:focus,
&.btn-info:active,
&.btn-info:hover:active,
&.btn-info:hover:focus,
&.btn-info:focus:active,
&.btn-info:focus:active:hover {
border-color: $g5-pepper;
background-color: $g5-pepper;
color: $g8-storm !important;
}
&.btn-danger,
&.btn-danger:hover,
&.btn-danger:focus,
&.btn-danger:active,
&.btn-danger:hover:active,
&.btn-danger:hover:focus,
&.btn-danger:focus:active,
&.btn-danger:focus:active:hover {
border-color: $c-ruby;
background-color: $c-ruby;
color: $g2-kevlar !important;
}
&.btn-warning,
&.btn-warning:hover,
&.btn-warning:focus,
&.btn-warning:active,
&.btn-warning:hover:active,
&.btn-warning:hover:focus,
&.btn-warning:focus:active,
&.btn-warning:focus:active:hover {
border-color: $c-star;
background-color: $c-star;
color: $g2-kevlar !important;
}
}
/* Active state for buttons */
.btn-info.active {
&, &:hover, &:active, &:focus {
background-color: $g7-graphite;
color: $g20-white;
}
}
/*
Dark Inputs
----------------------------------------------
*/
.form-group {
margin-bottom: 9px;
}
.form-group label {
font-size: 12px;
line-height: 12px;
font-weight: 600;
margin-bottom: 4px;
padding: 0 13px;
@include no-user-select();
}
.form-control {
padding: 0 13px;
background-color: $g2-kevlar !important;
border-color: $g5-pepper !important;
color: $g15-platinum !important;
&:hover {
border-color: $g6-smoke !important;
}
&:focus {
border-color: $c-pool !important;
box-shadow: 0 0 6px 0px $c-pool !important;
color: $g20-white !important;
}
&--green:focus {
border-color: $c-rainforest !important;
box-shadow: 0 0 6px 0px $c-rainforest !important;
}
}
.form-helper {
margin: 4px 0 !important;
font-weight: 400 !important;
font-style: italic;
line-height: 16px !important;
}
.form-group-submit {
margin-top: 30px;
}
/* Placeholder Text */
.form-control,
textarea,
input {
&::-webkit-input-placeholder { color: $g9-mountain; font-weight: 500 !important; }
&::-moz-placeholder { color: $g9-mountain; font-weight: 500 !important; }
&:-ms-input-placeholder { color: $g9-mountain; font-weight: 500 !important; }
&:-moz-placeholder { color: $g9-mountain; font-weight: 500 !important; }
}
/* Text Selection Styling */
::selection {
background-color: $c-pool !important;
color: $g20-white !important;
}
::-moz-selection {
background-color: $c-pool !important;
color: $g20-white !important;
}
/* Green (Kapacitor) themed inputs */
.form-control--green {
color: $c-rainforest !important;
&:focus {color: $g20-white !important;}
&::selection {
background-color: $c-rainforest !important;
color: $g20-white !important;
}
&::-moz-selection {
background-color: $c-rainforest !important;
color: $g20-white !important;
}
}
/*
Dark Code Samples
----------------------------------------------
*/
code, pre {
font-family: 'RobotoMono', monospace !important;
}
code {
display: inline-block;
background-color: $g2-kevlar;
color: $c-comet;
border: 0 !important;
border-radius: 2px;
padding: 2.5px 5px 2.5px 4px;
margin: 0 1px 0 2px;
line-height: 11px;
}
/*
Dark Modals
----------------------------------------------
*/
.modal-backdrop {
background: $c-pool;
background: -moz-linear-gradient(-45deg, $c-pool 0%, $c-comet 100%);
background: -webkit-linear-gradient(-45deg, $c-pool 0%,$c-comet 100%);
background: linear-gradient(135deg, $c-pool 0%,$c-comet 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$c-pool', endColorstr='$c-comet',GradientType=1 );
}
.modal-content {
background-color: $g3-castle;
}
.modal-header,
.modal-body,
.modal-footer {
background-color: transparent;
color: $g13-mist;
padding-left: ($sidebar-width / 2);
padding-right: ($sidebar-width / 2);
}
.modal-body {
padding-top: 0;
padding-bottom: 0;
}
.modal-title {
color: $g18-cloud !important;
}
.modal-header .close {
transition: none;
outline: none;
&:before,
&:after {
display: block;
content: '';
width: 20px;
height: 2px;
border-radius: 1px;
background-color: $g7-graphite;
transition: background-color 0.25s ease;
position: absolute;
top: 50%;
left: 50%;
}
&:before { transform: translate(-50%,-50%) rotate(45deg); }
&:after { transform: translate(-50%,-50%) rotate(-45deg); }
&:hover {
cursor: pointer;
&:before,
&:after {
background-color: $g13-mist;
}
}
span {
display: none;
}
}
/*
Dark Toggles
----------------------------------------------
*/
$toggle-height-sm: 30px;
$toggle-font-sm: 13px;
$toggle-padding-sm: 9px;
$toggle-height-md: 30px;
$toggle-font-md: 13px;
$toggle-padding-md: 9px;
$toggle-border: 2px;
.toggle {
display: inline-block;
margin: 0;
width: auto;
padding: $toggle-border;
border-radius: 3px;
background-color: $g5-pepper;
font-size: 0;
white-space: nowrap;
@include no-user-select();
}
.toggle-btn {
border: 0;
border-radius: 1px;
display: inline-block;
vertical-align: middle;
width: auto;
background-color: $g3-castle;
color: $g11-sidewalk;
transition:
background-color 0.25s,
color 0.25s;
&:hover {
cursor: pointer;
color: $g14-chromium;
background-color: $g4-onyx;
}
&.active {
background-color: $g5-pepper;
color: $g18-cloud;
}
}
.toggle-sm {
height: $toggle-height-sm;
.toggle-btn {
height: ($toggle-height-sm - ($toggle-border * 2));
line-height: ($toggle-height-sm - ($toggle-border * 2));
padding: 0 16px;
font-size: $toggle-font-sm;
font-weight: 600;
}
}
/*
Static Form Controls
----------------------------------------------
*/
$form-static-checkbox-size: 16px;
.form-control-static {
border: 2px solid $g5-pepper;
height: auto;
border-radius: 4px;
padding: 7px 12px;
position: relative;
input[type="checkbox"] {
position: relative;
left: -9999px;
visibility: hidden;
width: 0;
height: 0;
margin: 0;
// Faux Checkbox
& + label {
font-size: 14px !important;
line-height: 16px;
color: $g11-sidewalk;
font-weight: 500;
transition: color 0.25s ease;
margin: 0;
padding: 0 0 0 (12px + $form-static-checkbox-size) !important;
user-select: none;
-ms-user-select: none;
-moz-user-selct: none;
-webkit-user-select: none;
&:before {
content: '';
position: absolute;
top: 50%;
left: 12px;
transform: translateY(-50%);
width: $form-static-checkbox-size;
height: $form-static-checkbox-size;
background-color: $g2-kevlar;
border: 2px solid $g5-pepper;
border-radius: 3px;
z-index: 2;
transition:
border-color 0.25s ease;
}
&:after {
content: '';
position: absolute;
top: 50%;
left: (12px + ($form-static-checkbox-size / 2));
transform: translate(-50%,-50%) scale(2,2);
opacity: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: $c-pool;
z-index: 3;
transition:
opacity 0.25s ease,
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&:hover {
cursor: pointer;
color: $g20-white;
&:before {
border-color: $g6-smoke;
}
}
}
// Faux Checkbox (Checked)
&:checked + label {
color: $g20-white;
&:after {
opacity: 1;
transform: translate(-50%,-50%) scale(1,1);
}
}
}
}
.form-control-static .radio {
margin: 0;
input[type="radio"] {
position: relative;
left: -9999px;
visibility: hidden;
width: 0;
height: 0;
margin: 0;
// Faux Checkbox
& + label {
font-size: 14px !important;
line-height: 16px;
color: $g11-sidewalk;
font-weight: 500;
transition: color 0.25s ease;
margin: 0;
padding: 0 0 0 (12px + $form-static-checkbox-size) !important;
user-select: none;
-ms-user-select: none;
-moz-user-selct: none;
-webkit-user-select: none;
&:before {
content: '';
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: $form-static-checkbox-size;
height: $form-static-checkbox-size;
background-color: $g2-kevlar;
border: 2px solid $g5-pepper;
border-radius: 50%;
z-index: 2;
transition:
border-color 0.25s ease;
}
&:after {
content: '';
position: absolute;
top: 50%;
left: ($form-static-checkbox-size / 2);
transform: translate(-50%,-50%) scale(2,2);
opacity: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: $c-pool;
z-index: 3;
transition:
opacity 0.25s ease,
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&:hover {
cursor: pointer;
color: $g20-white;
&:before {
border-color: $g6-smoke;
}
}
}
// Faux Checkbox (Checked)
&:checked + label {
color: $g20-white;
&:after {
opacity: 1;
transform: translate(-50%,-50%) scale(1,1);
}
}
}
}
.dark-checkbox {
input {
position: absolute;
left: -9999px;
visibility: hidden;
}
label {
display: inline-block;
width: $form-static-checkbox-size;
height: $form-static-checkbox-size;
background-color: $g1-raven;
border-radius: $radius-small;
position: relative;
vertical-align: middle;
margin: 0;
transition: background-color 0.25s ease;
}
label:hover {
cursor: pointer;
background-color: $g2-kevlar;
}
label:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
background-color: $c-pool;
border-radius: 50%;
transform: translate(-50%,-50%) scale(2,2);
opacity: 0;
z-index: 3;
transition:
opacity 0.25s ease,
transform 0.25s ease;
}
input:checked + label:after {
opacity: 1;
transform: translate(-50%,-50%) scale(1,1);
}
}
br {
@include no-user-select();
}

View File

@ -29,21 +29,6 @@
color: $g8-storm;
}
}
.btn-block.dropdown-toggle {
text-align: left;
position: relative;
.caret {
position: absolute;
top: 50%;
right: 18px;
transform: translateY(-50%);
}
& + .dropdown-menu {
width: 100%;
}
}
.modal {
form {
padding: 0;
@ -59,10 +44,6 @@
}
}
}
.form-group label,
.form-group label:hover {
cursor: default;
}
/*
Generic Empty State
@ -71,67 +52,25 @@
.generic-empty-state {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
color: $g12-forge;
padding: 20px 0;
h4, h5 {
font-weight: 400;
}
.icon {
margin-bottom: 11px;
}
}
// Tabs for switching between queries
.qeditor--tabs {
display: flex;
width: 100%;
justify-content: flex-start;
padding: 8px 9px 0 9px;
background-color: $query-editor-tab-inactive;
flex-wrap: wrap;
&-heading {
flex-basis: 100%;
width: 100%;
font-size: 12px;
color: $g9-mountain;
font-weight: 500;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
}
.qeditor--tab {
text-align: center;
background-color: $query-editor-tab-inactive;
color: $g13-mist;
height: 28px;
padding: 0 9px;
line-height: 28px;
font-size: 12px;
font-weight: 600;
border-radius: $radius-small $radius-small 0 0;
margin-right: 2px;
@include no-user-select();
transition:
color 0.25s ease,
background-color 0.25s ease;
&:hover {
cursor: pointer;
color: $g20-white;
}
&.active {
background-color: $query-editor-tab-active;
color: $g20-white;
}
}
/*
Loading Dots
----------------------------------------------
*/
.loading-dots {
position: absolute;
transform: translate(0,0);
@ -154,7 +93,6 @@
div:nth-child(3) {left: 100%; animation: refreshingSpinnerC 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;}
}
/*
Custom Tabs
----------------------------------------------
@ -206,7 +144,9 @@
@include no-user-select();
}
br {
@include no-user-select();
}
.select-source-page {
position: absolute;
@ -217,4 +157,4 @@
overflow: auto;
@include custom-scrollbar($g2-kevlar, $c-pool);
@include gradient-v($g2-kevlar, $g0-obsidian);
}
}