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 ### Bug Fixes
1. [#1530](https://github.com/influxdata/chronograf/pull/1530): Update query config field ordering to always match input query 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 ### 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. [#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. [#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. [#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] ## 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 ### UI Improvements
1. [#1451](https://github.com/influxdata/chronograf/pull/1451): Refactor scrollbars to support non-webkit browsers 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. [#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. [#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 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.2 [MIT](https://github.com/niklasvh/base64-arraybuffer)
* base64-arraybuffer 0.1.5 [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) * 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) * batch 0.5.3 [MIT](https://github.com/visionmedia/batch)
* bcrypt-pbkdf 1.0.0 [BSD-4-Clause]((none)) * bcrypt-pbkdf 1.0.0 [BSD-4-Clause]((none))
* benchmark 1.0.0 [MIT](https://github.com/bestiejs/benchmark.js) * 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, Href: task.Link.Href,
HrefOutput: c.HrefOutput(kapaID), HrefOutput: c.HrefOutput(kapaID),
TICKScript: script, TICKScript: script,
Rule: rule, Rule: c.Reverse(kapaID, script),
}, nil }, nil
} }
@ -215,6 +215,22 @@ func (c *Client) All(ctx context.Context) (map[string]chronograf.AlertRule, erro
return alerts, nil 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 // Get returns a single alert in kapacitor
func (c *Client) Get(ctx context.Context, id string) (chronograf.AlertRule, error) { func (c *Client) Get(ctx context.Context, id string) (chronograf.AlertRule, error) {
kapa, err := c.kapaClient(c.URL, c.Username, c.Password) 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) script := chronograf.TICKScript(task.TICKscript)
rule, err := Reverse(script) return c.Reverse(task.ID, script), nil
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
} }
// Update changes the tickscript of a given id. // 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, Href: task.Link.Href,
HrefOutput: c.HrefOutput(task.ID), HrefOutput: c.HrefOutput(task.ID),
TICKScript: script, TICKScript: script,
Rule: rule, Rule: c.Reverse(task.ID, script),
}, nil }, nil
} }

View File

@ -15,15 +15,15 @@ type MockKapa struct {
ResTasks []client.Task ResTasks []client.Task
Error error Error error
client.CreateTaskOptions *client.CreateTaskOptions
client.Link client.Link
*client.TaskOptions *client.TaskOptions
*client.ListTasksOptions *client.ListTasksOptions
client.UpdateTaskOptions *client.UpdateTaskOptions
} }
func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) { func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) {
m.CreateTaskOptions = opt m.CreateTaskOptions = &opt
return m.ResTask, m.Error 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) { func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) {
m.Link = link m.Link = link
m.UpdateTaskOptions = opt if m.UpdateTaskOptions == nil {
m.UpdateTaskOptions = &opt
}
return m.ResTask, m.Error return m.ResTask, m.Error
} }
@ -49,6 +51,13 @@ func (m *MockKapa) DeleteTask(link client.Link) error {
return m.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) { func TestClient_AllStatus(t *testing.T) {
type fields struct { type fields struct {
URL string URL string
@ -160,9 +169,6 @@ func TestClient_AllStatus(t *testing.T) {
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.AllStatus() = %v, want %v", 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) { if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.AllStatus() = listTasksOptions %v, want %v", 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) { if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.All() = %#v, want %#v", 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) { if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.All() = listTasksOptions %v, want %v", 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) { if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Client.Get() =\n%#v\nwant\n%#v", 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) { if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) {
t.Errorf("Client.Get() = listTasksOptions %v, want %v", 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 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 // KapacitorRulesPut proxies PATCH to kapacitor
func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) { func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) {
id, err := paramID("kid", r) 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) Error(w, http.StatusInternalServerError, err.Error(), h.Logger)
return return
} }
res := newAlertResponse(task.Rule, task.TICKScript, task.Href, task.HrefOutput, "enabled", srv.SrcID, srv.ID)
res := newAlertResponse(req, task.TICKScript, task.Href, task.HrefOutput, "enabled", srv.SrcID, srv.ID)
encodeJSON(w, http.StatusOK, res, h.Logger) 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 { import {
chooseTrigger, chooseTrigger,
addEvery,
removeEvery,
updateRuleValues, updateRuleValues,
updateDetails, updateDetails,
updateMessage, updateMessage,
@ -38,6 +40,23 @@ describe('Kapacitor.Reducers.rules', () => {
expect(newState[ruleID].values).to.equal(defaultRuleConfigs.threshold) 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', () => { it('can update the values', () => {
const ruleID = 1 const ruleID = 1
const initialState = { const initialState = {
@ -50,11 +69,17 @@ describe('Kapacitor.Reducers.rules', () => {
} }
const newDeadmanValues = {duration: '5m'} 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) expect(newState[ruleID].values).to.equal(newDeadmanValues)
const newRelativeValues = {func: 'max', change: 'change'} 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].trigger).to.equal('relative')
expect(finalState[ruleID].values).to.equal(newRelativeValues) expect(finalState[ruleID].values).to.equal(newRelativeValues)
}) })
@ -110,7 +135,10 @@ describe('Kapacitor.Reducers.rules', () => {
.services('a b c') .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')` 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]) 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) expect(newState[ruleID].status).to.equal(status)
}) })
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ const UsersTable = ({
onUpdateRoles, onUpdateRoles,
onUpdatePassword, onUpdatePassword,
}) => ( }) => (
<div className="panel panel-info"> <div className="panel panel-default">
<FilterBar <FilterBar
type="users" type="users"
onFilter={onFilter} onFilter={onFilter}
@ -28,13 +28,13 @@ const UsersTable = ({
onClickCreate={onClickCreate} onClickCreate={onClickCreate}
/> />
<div className="panel-body"> <div className="panel-body">
<table className="table v-center admin-table"> <table className="table v-center admin-table table-highlight">
<thead> <thead>
<tr> <tr>
<th>User</th> <th>User</th>
{hasRoles && <th>Roles</th>} <th>Password</th>
<th>Permissions</th> {hasRoles && <th className="admin-table--left-offset">Roles</th>}
<th /> <th className="admin-table--left-offset">Permissions</th>
<th /> <th />
</tr> </tr>
</thead> </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>
<div className="panel-body"> <div className="panel-body">
{this.props.alerts.length {this.props.alerts.length
? <table className="table v-center"> ? <table className="table v-center table-highlight">
<thead> <thead>
<tr> <tr>
<th <th
@ -149,9 +149,9 @@ const AlertsTable = React.createClass({
</tbody> </tbody>
</table> </table>
: <div className="generic-empty-state"> : <div className="generic-empty-state">
<h4 className="no-user-select"> <h5 className="no-user-select">
Alerts appear here when you have Rules Alerts appear here when you have Rules
</h4> </h5>
<br /> <br />
<Link <Link
to={`/sources/${id}/alert-rules/new`} to={`/sources/${id}/alert-rules/new`}
@ -201,7 +201,7 @@ const SearchBar = React.createClass({
value={this.state.searchTerm} value={this.state.searchTerm}
/> />
<div className="input-group-addon"> <div className="input-group-addon">
<span className="icon search" aria-hidden="true" /> <span className="icon search" />
</div> </div>
</div> </div>
) )

View File

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

View File

@ -36,15 +36,20 @@ class DashboardEditHeader extends Component {
return ( return (
<div className="page-header full-width"> <div className="page-header full-width">
<div className="page-header__container"> <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 <input
className="page-header--editing" className="page-header--editing"
name="name" name="name"
autoFocus={true}
value={name} value={name}
placeholder="Name this Dashboard" placeholder="Name this Dashboard"
onChange={e => this.handleChange(e.target.value)} onChange={e => this.handleChange(e.target.value)}
onKeyUp={this.handleKeyUp} onKeyUp={this.handleKeyUp}
autoFocus={true}
spellCheck={false}
autoComplete="off" autoComplete="off"
/> />
</form> </form>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,6 +66,8 @@ const TagListItem = React.createClass({
value={this.state.filterText} value={this.state.filterText}
onChange={this.handleFilterText} onChange={this.handleFilterText}
onKeyUp={this.handleEscape} onKeyUp={this.handleEscape}
spellCheck={false}
autoComplete={false}
/> />
<span className="icon search" /> <span className="icon search" />
</div> </div>
@ -107,7 +109,7 @@ const TagListItem = React.createClass({
{tagItemLabel} {tagItemLabel}
</span> </span>
<div <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, active: this.props.isUsingGroupBy,
})} })}
onClick={this.handleGroupBy} onClick={this.handleGroupBy}

View File

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

View File

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

View File

@ -190,21 +190,20 @@ export const HostPage = React.createClass({
> >
{Object.keys(hosts).map((host, i) => { {Object.keys(hosts).map((host, i) => {
return ( return (
<li key={i}> <li className="dropdown-item" key={i}>
<Link <Link to={`/sources/${id}/hosts/${host + appParam}`}>
to={`/sources/${id}/hosts/${host + appParam}`}
className="role-option"
>
{host} {host}
</Link> </Link>
</li> </li>
) )
})} })}
</DashboardHeader> </DashboardHeader>
<FancyScrollbar className={classnames({ <FancyScrollbar
'page-contents': true, className={classnames({
'presentation-mode': inPresentationMode, 'page-contents': true,
})}> 'presentation-mode': inPresentationMode,
})}
>
<div className="container-fluid full-width dashboard"> <div className="container-fluid full-width dashboard">
{layouts.length > 0 ? this.renderLayouts(layouts) : ''} {layouts.length > 0 ? this.renderLayouts(layouts) : ''}
</div> </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) { export function updateRuleValues(ruleID, trigger, values) {
return { return {
type: 'UPDATE_RULE_VALUES', 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 MeasurementList from '../../data_explorer/components/MeasurementList'
import FieldList from '../../data_explorer/components/FieldList' import FieldList from '../../data_explorer/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
export const DataSection = React.createClass({ export const DataSection = React.createClass({
propTypes: { propTypes: {
source: PropTypes.shape({ source: PropTypes.shape({
@ -27,6 +29,8 @@ export const DataSection = React.createClass({
groupByTime: PropTypes.func.isRequired, groupByTime: PropTypes.func.isRequired,
toggleTagAcceptance: PropTypes.func.isRequired, toggleTagAcceptance: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
onAddEvery: PropTypes.func.isRequired,
onRemoveEvery: PropTypes.func.isRequired,
timeRange: PropTypes.shape({}).isRequired, timeRange: PropTypes.shape({}).isRequired,
}, },
@ -53,6 +57,11 @@ export const DataSection = React.createClass({
handleToggleField(field) { handleToggleField(field) {
this.props.actions.toggleField(this.props.query.id, field, true) 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) { handleGroupByTime(time) {
@ -61,6 +70,7 @@ export const DataSection = React.createClass({
handleApplyFuncsToField(fieldFunc) { handleApplyFuncsToField(fieldFunc) {
this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc) this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc)
this.props.onAddEvery(defaultEveryFrequency)
}, },
handleChooseTag(tag) { handleChooseTag(tag) {
@ -80,13 +90,13 @@ export const DataSection = React.createClass({
const statement = query.rawText || buildInfluxQLQuery({lower}, query) const statement = query.rawText || buildInfluxQLQuery({lower}, query)
return ( return (
<div className="kapacitor-rule-section kapacitor-metric-selector"> <div className="rule-section">
<h3 className="rule-section-heading">Select a Time Series</h3> <h3 className="rule-section--heading">Select a Time Series</h3>
<div className="rule-section-body"> <div className="rule-section--body">
<pre> <pre className="rule-section--border-bottom">
<code <code
className={classnames({ className={classnames({
'kapacitor-metric-placeholder': !statement, 'metric-placeholder': !statement,
})} })}
> >
{statement || 'Build a query below'} {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"> <div className="form-group form-group-submit col-xs-12 text-center">
<button <button
className="btn btn-info" className="btn btn-default"
type="button" type="button"
onClick={onReset} onClick={onReset}
> >

View File

@ -70,6 +70,8 @@ export const KapacitorRule = React.createClass({
source={source} source={source}
query={query} query={query}
actions={queryActions} actions={queryActions}
onAddEvery={this.handleAddEvery}
onRemoveEvery={this.handleRemoveEvery}
/> />
<ValuesSection <ValuesSection
rule={rule} 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() { validationError() {
const {rule, query} = this.props const {rule, query} = this.props
if (rule.trigger === 'deadman') { if (rule.trigger === 'deadman') {

View File

@ -33,7 +33,7 @@ export const RuleGraph = React.createClass({
if (!queryText) { if (!queryText) {
return ( 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> <p>Select a <strong>Time-Series</strong> to preview on a graph</p>
</div> </div>
) )

View File

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

View File

@ -53,16 +53,16 @@ export const RuleMessage = React.createClass({
const selectedAlert = rule.alerts[0] || alerts[0].text const selectedAlert = rule.alerts[0] || alerts[0].text
return ( return (
<div className="kapacitor-rule-section"> <div className="rule-section">
<h3 className="rule-section-heading">Alert Message</h3> <h3 className="rule-section--heading">Alert Message</h3>
<div className="rule-section-body"> <div className="rule-section--body">
<div className="kapacitor-values-tabs"> <div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send this Alert to:</p> <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 => ( {alerts.map(alert => (
<li <li
key={alert.text} key={alert.text}
className={classnames('btn tab', { className={classnames({
active: alert.text === selectedAlert, active: alert.text === selectedAlert,
})} })}
onClick={() => this.handleChooseAlert(alert)} onClick={() => this.handleChooseAlert(alert)}
@ -78,25 +78,27 @@ export const RuleMessage = React.createClass({
rule={rule} rule={rule}
/> />
{selectedAlert === 'smtp' {selectedAlert === 'smtp'
? <div className="alert-message--email-body"> ? <div className="rule-section--border-bottom">
<textarea <textarea
className="alert-text details" className="form-control form-malachite monotype rule-builder--message"
placeholder="Email body text goes here" placeholder="Email body text goes here"
ref={r => (this.details = r)} ref={r => (this.details = r)}
onChange={() => onChange={() =>
actions.updateDetails(rule.id, this.details.value)} actions.updateDetails(rule.id, this.details.value)}
value={rule.details} value={rule.details}
spellCheck={false}
/> />
</div> </div>
: null} : null}
<textarea <textarea
className="alert-text message" className="form-control form-malachite monotype rule-builder--message"
ref={r => (this.message = r)} ref={r => (this.message = r)}
onChange={() => actions.updateMessage(rule.id, this.message.value)} onChange={() => actions.updateMessage(rule.id, this.message.value)}
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields &quot;value&quot; }}" placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields &quot;value&quot; }}"
value={rule.message} 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> <p>Templates:</p>
{Object.keys(templates).map(t => { {Object.keys(templates).map(t => {
return ( return (
@ -137,7 +139,15 @@ const CodeData = React.createClass({
const {onClickTemplate, template} = this.props const {onClickTemplate, template} = this.props
const {label, text} = template 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 null
} }
return ( return (
<div className="rule-section--item alert-message--config"> <div className="rule-section--row rule-section--border-bottom">
<p>{DEFAULT_ALERT_LABELS[alert]}</p> <p>{DEFAULT_ALERT_LABELS[alert]}</p>
<input <input
id="alert-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" type="text"
placeholder={DEFAULT_ALERT_PLACEHOLDERS[alert]} placeholder={DEFAULT_ALERT_PLACEHOLDERS[alert]}
onChange={e => updateAlertNodes(rule.id, alert, e.target.value)} 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)) const initialIndex = TABS.indexOf(_.startCase(rule.trigger))
return ( return (
<div className="kapacitor-rule-section"> <div className="rule-section">
<h3 className="rule-section-heading">Rule Conditions</h3> <h3 className="rule-section--heading">Rule Conditions</h3>
<div className="rule-section-body"> <div className="rule-section--body">
<Tabs initialIndex={initialIndex} onSelect={this.handleChooseTrigger}> <Tabs initialIndex={initialIndex} onSelect={this.handleChooseTrigger}>
<TabList isKapacitorTabs="true"> <TabList isKapacitorTabs="true">
{TABS.map(tab => <Tab key={tab}>{tab}</Tab>)} {TABS.map(tab => (
<Tab key={tab} isKapacitorTab={true}>{tab}</Tab>
))}
</TabList> </TabList>
<TabPanels> <TabPanels>
@ -99,29 +101,38 @@ const Threshold = React.createClass({
const operators = mapToItems(OPERATORS, 'operator') const operators = mapToItems(OPERATORS, 'operator')
return ( return (
<div className="value-selector"> <div className="rule-section--row rule-section--border-bottom">
<p>Send Alert where</p> <p>Send Alert where</p>
<span> <span className="rule-builder--metric">
{query.fields.length ? query.fields[0].field : 'Select a Time-Series'} {query.fields.length ? query.fields[0].field : 'Select a Time-Series'}
</span> </span>
<p>is</p> <p>is</p>
<Dropdown <Dropdown
className="size-176 dropdown-kapacitor" className="dropdown-180"
menuClass="dropdown-malachite"
items={operators} items={operators}
selected={operator} selected={operator}
onChoose={this.handleDropdownChange} onChoose={this.handleDropdownChange}
/> />
<input <input
className="form-control input-sm size-166 form-control--green" className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
type="text" type="text"
spellCheck="false" spellCheck="false"
ref={r => (this.valueInput = r)} ref={r => (this.valueInput = r)}
defaultValue={value} defaultValue={value}
onKeyUp={this.handleInputChange} onKeyUp={this.handleInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/> />
{(operator === 'inside range' || operator === 'outside range') && {(operator === 'inside range' || operator === 'outside range') &&
<input <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" type="text"
spellCheck="false" spellCheck="false"
ref={r => (this.valueRangeInput = r)} ref={r => (this.valueRangeInput = r)}
@ -162,30 +173,34 @@ const Relative = React.createClass({
const operators = mapToItems(OPERATORS, 'operator') const operators = mapToItems(OPERATORS, 'operator')
return ( return (
<div className="value-selector"> <div className="rule-section--row rule-section--border-bottom">
<p>Send Alert when</p> <p>Send Alert when</p>
<Dropdown <Dropdown
className="size-106 dropdown-kapacitor" className="dropdown-110"
menuClass="dropdown-malachite"
items={changes} items={changes}
selected={change} selected={change}
onChoose={this.handleDropdownChange} onChoose={this.handleDropdownChange}
/> />
<p>compared to previous</p> <p>compared to previous</p>
<Dropdown <Dropdown
className="size-66 dropdown-kapacitor" className="dropdown-80"
menuClass="dropdown-malachite"
items={shifts} items={shifts}
selected={shift} selected={shift}
onChoose={this.handleDropdownChange} onChoose={this.handleDropdownChange}
/> />
<p>is</p> <p>is</p>
<Dropdown <Dropdown
className="size-176 dropdown-kapacitor" className="dropdown-160"
menuClass="dropdown-malachite"
items={operators} items={operators}
selected={operator} selected={operator}
onChoose={this.handleDropdownChange} onChoose={this.handleDropdownChange}
/> />
<input <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)} ref={r => (this.input = r)}
defaultValue={value} defaultValue={value}
onKeyUp={this.handleInputChange} onKeyUp={this.handleInputChange}
@ -193,7 +208,7 @@ const Relative = React.createClass({
type="text" type="text"
spellCheck="false" spellCheck="false"
/> />
<p>{change === CHANGES[1] ? '%' : ''}</p> {change === CHANGES[1] ? <p>%</p> : null}
</div> </div>
) )
}, },
@ -219,10 +234,11 @@ const Deadman = React.createClass({
}) })
return ( return (
<div className="value-selector"> <div className="rule-section--row">
<p>Send Alert if Data is missing for</p> <p>Send Alert if Data is missing for</p>
<Dropdown <Dropdown
className="size-66 dropdown-kapacitor" className="dropdown-80"
menuClass="dropdown-malachite"
items={periods} items={periods}
selected={this.props.rule.values.period} selected={this.props.rule.values.period}
onChoose={this.handleChange} onChoose={this.handleChange}

View File

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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import _ from 'lodash' 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 {bindActionCreators} from 'redux'
import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index' import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index'
import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants'
@ -23,6 +25,8 @@ export const KapacitorRulePage = React.createClass({
loadDefaultRule: PropTypes.func.isRequired, loadDefaultRule: PropTypes.func.isRequired,
fetchRule: PropTypes.func.isRequired, fetchRule: PropTypes.func.isRequired,
chooseTrigger: PropTypes.func.isRequired, chooseTrigger: PropTypes.func.isRequired,
addEvery: PropTypes.func.isRequired,
removeEvery: PropTypes.func.isRequired,
updateRuleValues: PropTypes.func.isRequired, updateRuleValues: PropTypes.func.isRequired,
updateMessage: PropTypes.func.isRequired, updateMessage: PropTypes.func.isRequired,
updateAlerts: PropTypes.func.isRequired, updateAlerts: PropTypes.func.isRequired,
@ -76,7 +80,7 @@ export const KapacitorRulePage = React.createClass({
.catch(() => { .catch(() => {
addFlashMessage({ addFlashMessage({
type: 'error', 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: '', message: '',
alerts: [], alerts: [],
alertNodes: [], alertNodes: [],
every: '30s', every: null,
name: 'Untitled Rule', 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': { case 'UPDATE_RULE_VALUES': {
const {ruleID, trigger, values} = action.payload const {ruleID, trigger, values} = action.payload
return Object.assign({}, state, { return Object.assign({}, state, {

View File

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

View File

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

View File

@ -46,15 +46,15 @@ class CustomTimeRangeDropdown extends Component {
return ( return (
<div <div
className={classnames('custom-time-range', {show: isVisible})} className={classnames('custom-time-range', {open: isVisible})}
style={{display: 'flex'}} style={{display: 'flex'}}
> >
<button <button
className="btn btn-sm btn-info custom-time-range--btn" className="btn btn-sm btn-default dropdown-toggle"
onClick={onToggle} onClick={onToggle}
> >
<span className="icon clock" /> <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" /> <span className="caret" />
</button> </button>
<div className="custom-time--container"> <div className="custom-time--container">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,9 +8,20 @@ export const Tab = React.createClass({
onClick: func, onClick: func,
isDisabled: bool, isDisabled: bool,
isActive: bool, isActive: bool,
isKapacitorTab: bool,
}, },
render() { 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 ( return (
<div <div
className={classnames('btn tab', {active: this.props.isActive})} className={classnames('btn tab', {active: this.props.isActive})}
@ -47,9 +58,11 @@ export const TabList = React.createClass({
if (this.props.isKapacitorTabs === 'true') { if (this.props.isKapacitorTabs === 'true') {
return ( return (
<div className="kapacitor-values-tabs"> <div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Alert Type</p> <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> </div>
) )
} }

View File

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

View File

@ -1,21 +1,36 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import classnames from 'classnames'
const YesNoButtons = ({onConfirm, onCancel}) => ( const YesNoButtons = ({onConfirm, onCancel, buttonSize}) => (
<div> <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" /> <span className="icon remove" />
</button> </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" /> <span className="icon checkmark" />
</button> </button>
</div> </div>
) )
const {func} = PropTypes const {func, string} = PropTypes
YesNoButtons.propTypes = { YesNoButtons.propTypes = {
onConfirm: func.isRequired, onConfirm: func.isRequired,
onCancel: func.isRequired, onCancel: func.isRequired,
buttonSize: string,
}
YesNoButtons.defaultProps = {
buttonSize: 'btn-sm',
} }
export default YesNoButtons export default YesNoButtons

View File

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

View File

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

View File

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

View File

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

View File

@ -6,16 +6,6 @@
.custom-time-range { .custom-time-range {
position: relative; 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 { .custom-time--container {
display: none; display: none;
position: absolute; position: absolute;
@ -242,8 +232,8 @@ $rd-cell-size: 30px;
} }
/* Show State */ /* Open State */
.custom-time-range.show { .custom-time-range.open {
.custom-time--container { .custom-time--container {
display: flex; display: flex;
} }

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

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 { .dropdown-toggle {
height: 38px; height: 38px;
padding-left: 0; padding-left: 0;
padding-right: (11px + 12px); // caret width + offset
padding-top: 0;
padding-bottom: 0;
min-width: 50px; min-width: 50px;
width: auto; width: auto;
border: 0; border: 0;
background-color: transparent; background-color: transparent;
text-transform: none; text-transform: none;
padding-right: (11px + 12px); // caret width + offset
font-size: $page-header-size; font-size: $page-header-size;
font-weight: $page-header-weight; font-weight: $page-header-weight;
transition: color 0.25s ease; transition: color 0.25s ease;

View File

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

View File

@ -114,9 +114,9 @@
transition: color 0.25s ease; transition: color 0.25s ease;
color: $g9-mountain; color: $g9-mountain;
} }
& > input { & > input.form-control {
order: 2; order: 2;
padding: 0 8px 0 24px; padding-left: 24px;
border-color: $g6-smoke !important; border-color: $g6-smoke !important;
&:hover {border-color: $g7-graphite !important;} &:hover {border-color: $g7-graphite !important;}
@ -172,13 +172,6 @@
.query-builder--list-item {padding: 0;} .query-builder--list-item {padding: 0;}
.query-builder--filter {margin-bottom: 4px;} .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 */ /* Toggle for grouping by tags in tags list */
.group-by-tag { .group-by-tag {
visibility: hidden; visibility: hidden;
@ -198,9 +191,6 @@
.query-builder--list-item.active .group-by-tag { .query-builder--list-item.active .group-by-tag {
visibility: visible; visibility: visible;
} }
.query-builder--db-dropdown { .query-builder--db-dropdown {
display: inline-block; display: inline-block;
} }

View File

@ -88,14 +88,6 @@
.dropdown.query-editor--templates { .dropdown.query-editor--templates {
margin: 0 4px 0 0 ; 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 { .dropdown-menu {
left: initial; left: initial;
right: 0; right: 0;

View File

@ -8,7 +8,7 @@
.query-maker { .query-maker {
height: 100%; height: 100%;
margin: 0 $explorer-page-padding; margin: 0 $page-wrapper-padding;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;

View File

@ -1,7 +1,13 @@
.alert-value-set { .redacted-input {
padding: 6px 13px 0; height: 38px;
align-items: center;
span a { justify-content: space-between;
margin-left: 10px; }
} .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 // 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 Sortable Tables
---------------------------------------------- ----------------------------------------------

View File

@ -36,7 +36,7 @@ $template-control-dropdown-min-width: 146px;
@include no-user-select(); @include no-user-select();
white-space: nowrap; white-space: nowrap;
} }
.template-control--manage { button.btn.template-control--manage {
margin: 7px 8px; margin: 7px 8px;
} }
.template-control--controls { .template-control--controls {
@ -64,38 +64,12 @@ $template-control-dropdown-min-width: 146px;
margin: 0; margin: 0;
flex: 1 0 0; flex: 1 0 0;
} }
.dropdown-toggle { .dropdown-toggle {
border-radius: 0 0 $radius-small $radius-small; border-radius: 0 0 $radius-small $radius-small;
width: 100%; width: 100%;
font-size: 12px; font-size: 12px;
font-family: $code-font; 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 { .template-control--label {
@include no-user-select(); @include no-user-select();

View File

@ -191,11 +191,6 @@ $tvmp-table-gutter: 8px;
.btn-edit { .btn-edit {
order: 1; 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;} > .btn {margin-left: $tvmp-table-gutter;}
@ -204,17 +199,9 @@ $tvmp-table-gutter: 8px;
/* Janky, but doing this quick & dirty for now */ /* Janky, but doing this quick & dirty for now */
.btn-danger { .btn-danger {
order: 2; order: 2;
height: 30px !important;
line-height: 30px !important;
padding: 0 9px !important;
font-size: 13px;
} }
.confirm-buttons > .btn { .confirm-buttons > .btn {
height: 30px !important;
width: 30px !important;
margin-left: $tvmp-table-gutter !important; margin-left: $tvmp-table-gutter !important;
font-size: 13px;
padding: 0 !important;
} }
/* Hide the edit button when confirming a delete */ /* Hide the edit button when confirming a delete */
.confirm-buttons + .btn-edit { .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 { .page-header__right {
display: flex; display: flex;
align-items: center; align-items: center;
> *:only-child {
margin: 0;
}
} }
.page-header__left { .page-header__left {
justify-content: flex-start; justify-content: flex-start;
@ -49,10 +45,15 @@ $page-header-weight: 400 !important;
.page-header__right { .page-header__right {
justify-content: flex-end; justify-content: flex-end;
> * { > * {
margin: 0 0 0 4px; margin: 0 0 0 4px !important;
&:only-child {
margin-right: 0 !important;
}
} }
} }
.page-header__title { .page-header__title {
letter-spacing: 0;
text-transform: none; text-transform: none;
font-size: $page-header-size; font-size: $page-header-size;
font-weight: $page-header-weight; font-weight: $page-header-weight;

View File

@ -3,9 +3,9 @@
---------------------------------------------- ----------------------------------------------
*/ */
$sidebar-width: 60px;
$sidebar-menu-gutter: 30px; $sidebar-menu-gutter: 30px;
$sidebar-menu-indent: 13px; $sidebar-menu-indent: 13px;
$sidebar-radius: 4px;
$sidebar-hover: $g8-storm; $sidebar-hover: $g8-storm;
$sidebar-item-hover: $g7-graphite; $sidebar-item-hover: $g7-graphite;
@ -21,38 +21,6 @@ $sidebar-active-accent: $c-laser;
$sidebar-logo-bg: $g17-whisper; $sidebar-logo-bg: $g17-whisper;
$sidebar-logo-color: $g8-storm; $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 styles
.sidebar { .sidebar {
display: flex; display: flex;
@ -176,7 +144,7 @@ $sidebar-logo-color: $g8-storm;
} }
&__menu { &__menu {
border-radius: 0 $sidebar-radius $sidebar-radius 0; border-radius: 0 $radius $radius 0;
background: $sidebar-hover; background: $sidebar-hover;
opacity: 0; opacity: 0;
transition: opacity 0.25s ease; transition: opacity 0.25s ease;
@ -237,12 +205,12 @@ $sidebar-logo-color: $g8-storm;
// Rounding top outside corner to match container // Rounding top outside corner to match container
&:first-child { &:first-child {
border-top-right-radius: $sidebar-radius; border-top-right-radius: $radius;
} }
// Rounding bottom outside corner of match container // Rounding bottom outside corner of match container
&:last-child { &:last-child {
border-bottom-right-radius: $sidebar-radius; border-bottom-right-radius: $radius;
} }
// Used for sub-navigation // Used for sub-navigation
@ -314,5 +282,4 @@ $sidebar-logo-color: $g8-storm;
font-weight: 400; font-weight: 400;
padding: 0px $sidebar-menu-gutter; padding: 0px $sidebar-menu-gutter;
} }
} }

View File

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

View File

@ -1,14 +1,5 @@
$radius: 4px; $radius: 4px;
$radius-small: 3px; $radius-small: 3px;
$page-wrapper-padding: 60px;
// Sidebar
$sidebar-width: 60px;
$page-wrapper-padding: 58px;
$page-wrapper-max-width: 1300px; $page-wrapper-max-width: 1300px;
$current-user-height: 56px;
$cluster-switcher-height: 56px;
$chronograf-page-header-height: 60px; $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 { .admin-tabs {
padding-right: 0; padding-right: 0;
& + div {padding-left: 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;
}
}
} }
.admin-tabs .btn-group { .admin-tabs .btn-group {
margin: 0; 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
---------------------------------------------- ----------------------------------------------
*/ */
.admin-table { .admin-table .dropdown-toggle {
.multi-select-dropdown { background-color: transparent;
width: 100%; font-weight: 600;
min-width: 150px; color: $g14-chromium;
} transition: none !important;
.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;
.caret {opacity: 0;} .caret {opacity: 0;}
.multi-select-dropdown__label {left: 0;} }
} .admin-table--multi-select-empty .dropdown-toggle {
.open .dropdown-toggle .multi-select-dropdown__label {left: 9px;} color: $g8-storm;
tbody tr:hover { }
.admin-table--hidden { .admin-table tbody tr:hover .dropdown-toggle {
visibility: visible; color: $g20-white !important;
} background-color: $c-pool;
.dropdown-toggle {
color: $g20-white !important;
background-color: $c-pool;
font-weight: 600;
.caret {opacity: 1;} .caret {opacity: 1;}
.multi-select-dropdown__label {left: 9px;}
&:hover { &:hover {
transition: background-color 0.25s ease; transition: background-color 0.25s ease;
background-color: $c-laser; 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;}
} }
} }
.admin-table--edit-row { table > tbody > tr > td.admin-table--left-offset,
background-color: $g4-onyx; 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 { .admin-table--change-pw {
width: 100%; display: flex;
margin: 0 !important; flex-wrap: nowrap;
display: flex !important;
justify-content: space-between;
> input { .form-control {
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;
margin: 0 4px 0 0; margin: 0 4px 0 0;
min-width: 110px; flex: 1 0 0;
font-size: 12px;
} }
}
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 { > .form-control {
display: block !important; flex: 1 0 0;
margin-right: 4px;
} }
} }
/*
Database Manager
----------------------------------------------
*/
.db-manager { .db-manager {
margin-bottom: 8px; margin-bottom: 8px;
.db-manager-header--actions { &:last-child {margin-bottom: 0;}
display: none; .db-manager-header--actions {display: none;}
} &:hover .db-manager-header--actions {display: block;}
&:last-child {
margin-bottom: 0;
}
&:hover .db-manager-header--actions {
display: block;
}
} }
.db-manager-header { .db-manager-header {
padding: 0 11px; padding: 0 11px;
@ -184,21 +146,18 @@
h4 { h4 {
margin: 0px; margin: 0px;
color: $c-potassium; color: $c-potassium;
font-size: 17px; font-size: 16px;
font-family: $code-font; font-family: $code-font;
padding-left: 6px; padding-left: 6px;
width: auto;
} }
} }
.db-manager-header--edit { .db-manager-header--edit {
justify-content: flex-start; justify-content: flex-start;
.form-control { .form-control {
height: 22px; margin: 0 8px 0 0;
padding: 0 6px; flex: 1 0 0;
margin: 0 4px 0 0;
min-width: 110px;
font-size: 12px;
width: 50%;
} }
} }
@ -206,20 +165,6 @@
background-color: $g4-onyx; background-color: $g4-onyx;
padding: 9px 11px; padding: 9px 11px;
border-radius: 0 0 $radius-small $radius-small; border-radius: 0 0 $radius-small $radius-small;
}
.table-highlight > tbody > tr:hover {background-color: $g5-pepper;}
.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;
}
} }

View File

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

View File

@ -1,47 +1,230 @@
/* /*
Kapacitor Rule Builder Kapacitor Rule Builder
---------------------------------------------- ---------------------------------------------------------------------------
*/ */
$kapacitor-page-padding: ($chronograf-page-header-height / 2); $rule-builder--accent-color: $c-rainforest;
$kapacitor-page-gutter: 46px; $rule-builder--left-gutter: 46px;
$kapacitor-dot-size: 18px; $rule-builder--section-gap: ($chronograf-page-header-height / 2);
$kapacitor-line-width: 3px; $rule-builder--section-bg: $g3-castle;
$metric-selector-height: 156px; $rule-builder--section-border: $g2-kevlar;
$kap-padding-sm: 8px; $rule-builder--dot: 18px;
$kap-padding-md: 13px; $rule-builder--accent-line-width: 3px;
$kap-padding-lg: 24px; $rule-builder--accent-line-color: $g5-pepper;
$kap-radius-lg: 5px; $rule-builder--font-size: 13px;
$kap-input-height: 30px; $rule-builder--query-builder-height: 240px;
$kapacitor-graphic-color: $g3-castle; $rule-builder--padding-sm: 8px;
$kapacitor-divider-color: $g2-kevlar; $rule-builder--padding-md: 13px;
$kapacitor-accent: $c-rainforest; $rule-builder--padding-lg: 24px;
$kap-line-color: $g5-pepper; $rule-builder--radius-lg: 5px;
$kap-dot-color: $c-rainforest;
$kapacitor-font-sm: 13px;
.rule-builder { .rule-builder {
width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; 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 { .rule-builder--graph {
margin-left: $kapacitor-page-gutter; margin-left: $rule-builder--left-gutter;
width: calc(100% - #{$kapacitor-page-gutter}); width: calc(100% - #{$rule-builder--left-gutter});
background-color: $kapacitor-graphic-color; background-color: $rule-builder--section-bg;
border-radius: 0 0 $kap-radius-lg $kap-radius-lg; border-radius: 0 0 $rule-builder--radius-lg $rule-builder--radius-lg;
padding: 0 $kap-padding-sm; padding: 0 $rule-builder--padding-sm;
height: (300px + ($kap-padding-sm * 2)); height: (300px + ($rule-builder--padding-sm * 2));
position: relative; position: relative;
& > div { & > div {
position: absolute; position: absolute;
top: 0; top: 0;
left: $kap-padding-sm; left: $rule-builder--padding-sm;
width: calc(100% - #{($kap-padding-sm * 2)}); width: calc(100% - #{($rule-builder--padding-sm * 2)});
height: 100%; height: 100%;
& > div { & > div {
@ -57,11 +240,11 @@ $kapacitor-font-sm: 13px;
display: block; display: block;
position: absolute; position: absolute;
transform: translateX(-50%); transform: translateX(-50%);
width: $kapacitor-line-width; width: $rule-builder--accent-line-width;
height: 100%; height: 100%;
background-color: $kap-line-color; background-color: $rule-builder--accent-line-color;
top: 0; top: 0;
left: (($kapacitor-dot-size / 2) - $kapacitor-page-gutter); left: (($rule-builder--dot / 2) - $rule-builder--left-gutter);
} }
.container--dygraph-legend { .container--dygraph-legend {
background-color: $g5-pepper; background-color: $g5-pepper;
@ -71,7 +254,7 @@ $kapacitor-font-sm: 13px;
} }
} }
} }
.rule-preview--graph-empty { .rule-builder--graph-empty {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -96,378 +279,42 @@ $kapacitor-font-sm: 13px;
} }
} }
.rule-section-heading { /*
margin: 0; Section 3 - Rule Message
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();
&:before, textarea.rule-builder--message {
&:after { border-color: $rule-builder--section-bg;
content: ''; background-color: $rule-builder--section-bg;
display: block; padding: $rule-builder--padding-sm ($rule-builder--padding-lg - 2px);
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%;
height: 100px; height: 100px;
min-width: 100%; min-width: 100%;
max-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; 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 { &:hover {
border-color: $g4-onyx; color: $rule-builder--accent-color;
} cursor: pointer;
&: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;
} }
} }
.alert-message--endpoint { /*
width: auto; Color coding for alerts in Alert History table
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;}
.alert-level-ok { .alert-level-ok {
&, &:hover {color: $c-rainforest !important;} &, &:hover {color: $c-rainforest !important;}
} }

View File

@ -39,8 +39,8 @@ $overlay-z: 100;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
flex: 0 0 $overlay-controls-height; flex: 0 0 $overlay-controls-height;
width: calc(100% - #{($explorer-page-padding * 2)}); width: calc(100% - #{($page-wrapper-padding * 2)});
left: $explorer-page-padding; left: $page-wrapper-padding;
border: 0; border: 0;
background-color: $g2-kevlar; background-color: $g2-kevlar;
border-radius: $radius $radius 0 0; border-radius: $radius $radius 0 0;
@ -54,13 +54,16 @@ $overlay-z: 100;
margin: 0 0 0 5px; margin: 0 0 0 5px;
} }
p { p {
width: auto;
font-weight: 600; font-weight: 600;
color: $g13-mist; color: $g13-mist;
margin: 0; margin: 0 6px 0 0;
@include no-user-select; @include no-user-select;
white-space: nowrap;
} }
} }
.overlay--graph-name { .overlay--graph-name {
width: auto;
margin: 0; margin: 0;
font-size: 17px; font-size: 17px;
font-weight: 400; font-weight: 400;
@ -78,28 +81,6 @@ $overlay-z: 100;
.overlay-controls .confirm-buttons { .overlay-controls .confirm-buttons {
margin-left: 32px; 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 */ /* Graph editing in Dashboards is a little smaller so the dash can be seen in the background */
.overlay-technology .graph { .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; 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 { .modal {
form { form {
padding: 0; padding: 0;
@ -59,10 +44,6 @@
} }
} }
} }
.form-group label,
.form-group label:hover {
cursor: default;
}
/* /*
Generic Empty State Generic Empty State
@ -71,67 +52,25 @@
.generic-empty-state { .generic-empty-state {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: center;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: $g12-forge; color: $g12-forge;
padding: 20px 0; padding: 20px 0;
h4, h5 {
font-weight: 400;
}
.icon { .icon {
margin-bottom: 11px; 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
---------------------------------------------- ----------------------------------------------
*/ */
.loading-dots { .loading-dots {
position: absolute; position: absolute;
transform: translate(0,0); 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;} div:nth-child(3) {left: 100%; animation: refreshingSpinnerC 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;}
} }
/* /*
Custom Tabs Custom Tabs
---------------------------------------------- ----------------------------------------------
@ -206,7 +144,9 @@
@include no-user-select(); @include no-user-select();
} }
br {
@include no-user-select();
}
.select-source-page { .select-source-page {
position: absolute; position: absolute;