Compare commits

...

28 Commits

Author SHA1 Message Date
Daniel Lee
9a91a882b4 update version to 4.6.0-beta3 2017-10-23 15:22:06 +02:00
Daniel Lee
6ad6131aaf plugins: expose dashboard impression store
(cherry picked from commit 90ef877e6e)
2017-10-23 15:20:29 +02:00
Sven Klemm
1f10928450 modify $__timeGroup macro so it can be used in select clause (#9527)
* modify $__timeGroup macro so it can be used in select clause

* fix $__interval_ms for postgres datasource

* use $__timeGroup macro in documentation

* fix annotation template query
remove title since its no longer used and add tags instead

* change __timeFilter macro to work on postgresql < 8.1 and redshift

(cherry picked from commit b2d880c6de)
2017-10-23 14:13:38 +02:00
Daniel Lee
0cd0aa19d6 plugins: fixes path issue on Windows
When loading a plugin and setting the path, an extra backslash sneaks
when running on Windows. Fixes #9597

(cherry picked from commit 7863a0417c)
2017-10-23 13:06:09 +02:00
bergquist
c2f2a43197 prometheus: enable gzip for /metrics endpoint
closes #9464

(cherry picked from commit 139f077453)
2017-10-23 09:37:41 +02:00
Torkel Ödegaard
7fe1ac5fe7 fix: fixed save to file button in export modal, fixes #9586
(cherry picked from commit 1e61eae9f4)
2017-10-19 12:54:24 +02:00
bergquist
aec448f7c6 mysql: add usage stats for mysql 2017-10-19 10:31:30 +02:00
Daniel Lee
2ce36c8670 pluginloader: esModule true for systemjs config
Supports importing a module's contents with the
'* as module' syntax. The latest version of SystemJS turns
it off per default which broke several plugins.

(cherry picked from commit 7cbb4020e9)
2017-10-19 09:04:22 +02:00
Alexander Zobnin
fb35d839c1 Fix heatmap Y axis rendering (#9580)
* heatmap: fix Y axis rendering, #9576

* heatmap: fix color legend rendering

(cherry picked from commit 92c67e8c83)
2017-10-18 15:23:12 +02:00
Mitsuhiro Tanda
c8f5d39d97 fix vector range
(cherry picked from commit 8a43d4e25c)
2017-10-18 14:07:44 +02:00
bergquist
34bc19359d prometheus: add builtin template variable as range vectors
add $__interval and $__interval_ms as range vectors to
prometheus editor

(cherry picked from commit c2c5f529f3)
2017-10-18 14:05:43 +02:00
Torkel Ödegaard
3dcae78126 fix: fixed prometheus step issue that caused browser crash, fixes #9575
(cherry picked from commit 8d68bd6bb9)
2017-10-18 13:29:02 +02:00
Torkel Ödegaard
054c7a154a fix: getting started panel and mark adding data source as done, fixes #9568
(cherry picked from commit 039fc2964a)
2017-10-18 12:04:52 +02:00
Alexander Zobnin
6f3d61f4d2 Fixes for annotations API (#9577)
* annotations: throw error if no text specified and set default time to Now() if empty, #9571

* annotations: fix saving graphite event with empty string tags

* docs: add /api/annotations/graphite endpoint docs, #9571

(cherry picked from commit 74e90d01ec)
2017-10-18 10:13:51 +02:00
bergquist
305f8c10e9 bump packagecloud script 2017-10-17 14:16:40 +02:00
Torkel Ödegaard
fff4cfd11e build: added imports of rxjs utility functions
(cherry picked from commit eda3cffe22)
2017-10-17 12:17:32 +02:00
Torkel Ödegaard
d20434f828 Merge branch 'v4.6.x' of github.com:grafana/grafana into v4.6.x 2017-10-17 12:15:26 +02:00
bergquist
1ef850fae0 prepare for v4.6.0-beta2 release 2017-10-17 11:17:34 +02:00
Mitsuhiro Tanda
2f7a59fb18 fix template variable expanding
(cherry picked from commit 0f87279ab8)
2017-10-17 11:16:36 +02:00
krise3k
07be20eeb3 annotations: quote reserved fields (#9550)
(cherry picked from commit 45a572ebd8)
2017-10-17 11:04:17 +02:00
Torkel Ödegaard
689b8d79df ux: align alert and btn colors
(cherry picked from commit b86ae3f0e8)
2017-10-17 10:57:34 +02:00
Torkel Ödegaard
b70c538633 fix: fixed color pickers that were broken in minified builds, fixes #9549
(cherry picked from commit 574afe8568)
2017-10-17 10:54:49 +02:00
Daniel Lee
769bc5df21 textpanel: fixes #9491
(cherry picked from commit 14241404ff)
2017-10-17 09:15:44 +02:00
Daniel Lee
4eb6d82254 csv: fix import for saveAs shim
After switch from systemjs to webpack, needed to import the
file-saver saveAs shim for it work. Fixes #9525

(cherry picked from commit f3ec139eab)
2017-10-17 09:14:45 +02:00
Daniel Lee
7935739eb3 plugins: expose more util and flot dependencies
Also, fix for coremodules export. Have to add the __esModule
attribute to fool SystemJS.

(cherry picked from commit eb4e71e2c6)
2017-10-17 09:13:58 +02:00
bergquist
7403fa0fa7 alert_tab: clear test result when testing rules
closes #9539

(cherry picked from commit 9de4d0fa6b)
2017-10-16 10:34:59 +02:00
Mitsuhiro Tanda
5ebfd1e5ab (cloudwatch) fix cloudwatch query error over 24h (#9536)
fix cloudwatch query error over 24h

(cherry picked from commit 7edc95cc35)

closes #9523
2017-10-16 08:32:13 +02:00
Mitsuhiro Tanda
a1a8c0fc07 show error message when cloudwatch datasource can't add
(cherry picked from commit 40b11654fd)
2017-10-16 08:10:24 +02:00
43 changed files with 279 additions and 129 deletions

View File

@@ -7,7 +7,12 @@
- UX changes to nav & side menu - UX changes to nav & side menu
- New dashboard grid layout system - New dashboard grid layout system
# 4.6.0 (unreleased) # 4.6.0-beta2 (2017-10-17)
## Fixes
* **ColorPicker**: Fix for color picker not showing [#9549](https://github.com/grafana/grafana/issues/9549)
# 4.6.0-beta1 (2017-10-13)
## New Features ## New Features
* **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin) * **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin)

View File

@@ -48,7 +48,7 @@ Macro example | Description
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > to_timestamp(1494410783) AND dateColumn < to_timestamp(1494497183)* *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > to_timestamp(1494410783) AND dateColumn < to_timestamp(1494497183)*
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)* *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int* *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int*extract(epoch from '5m'::interval)*
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183* *$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
@@ -94,26 +94,26 @@ Example with `metric` column
```sql ```sql
SELECT SELECT
min(time_date_time) as time, $__timeGroup(time_date_time,'5m') as time,
min(value_double), min(value_double),
'min' as metric 'min' as metric
FROM test_data FROM test_data
WHERE $__timeFilter(time_date_time) WHERE $__timeFilter(time_date_time)
GROUP BY metric1, (extract(epoch from time_date_time)/extract(epoch from $__interval::interval))::int GROUP BY time
ORDER BY time asc ORDER BY time
``` ```
Example with multiple columns: Example with multiple columns:
```sql ```sql
SELECT SELECT
min(time_date_time) as time, $__timeGroup(time_date_time,'5m') as time,
min(value_double) as min_value, min(value_double) as min_value,
max(value_double) as max_value max(value_double) as max_value
FROM test_data FROM test_data
WHERE $__timeFilter(time_date_time) WHERE $__timeFilter(time_date_time)
GROUP BY metric1, (extract(epoch from time_date_time)/extract(epoch from $__interval::interval))::int GROUP BY time
ORDER BY time asc ORDER BY time
``` ```
## Templating ## Templating

View File

@@ -120,6 +120,37 @@ Content-Type: application/json
PUT /api/annotations/1141 HTTP/1.1 PUT /api/annotations/1141 HTTP/1.1
Accept: application/json Accept: application/json
Content-Type: application/json Content-Type: application/json
{
"time":1507037197339,
"isRegion":true,
"timeEnd":1507180805056,
"text":"Annotation Description",
"tags":["tag3","tag4","tag5"]
}
```
## Delete Annotation By Id
`DELETE /api/annotation/:id`
Deletes the annotation that matches the specified id.
**Example Request**:
```http
DELETE /api/annotation/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
``` ```
## Delete Annotation By RegionId ## Delete Annotation By RegionId

View File

@@ -4,7 +4,7 @@
"company": "Grafana Labs" "company": "Grafana Labs"
}, },
"name": "grafana", "name": "grafana",
"version": "4.6.0-beta1", "version": "4.6.0-beta3",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://github.com/grafana/grafana.git" "url": "http://github.com/grafana/grafana.git"

View File

@@ -1,6 +1,6 @@
#! /usr/bin/env bash #! /usr/bin/env bash
deb_ver=4.6.0-beta1 deb_ver=4.6.0-beta2
rpm_ver=4.6.0-beta1 rpm_ver=4.6.0-beta2
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb

View File

@@ -1,7 +1,6 @@
package api package api
import ( import (
"fmt"
"strings" "strings"
"time" "time"
@@ -41,9 +40,22 @@ func GetAnnotations(c *middleware.Context) Response {
return Json(200, items) return Json(200, items)
} }
type CreateAnnotationError struct {
message string
}
func (e *CreateAnnotationError) Error() string {
return e.message
}
func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response { func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response {
repo := annotations.GetRepository() repo := annotations.GetRepository()
if cmd.Text == "" {
err := &CreateAnnotationError{"text field should not be empty"}
return ApiError(500, "Failed to save annotation", err)
}
item := annotations.Item{ item := annotations.Item{
OrgId: c.OrgId, OrgId: c.OrgId,
UserId: c.UserId, UserId: c.UserId,
@@ -55,6 +67,10 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response
Tags: cmd.Tags, Tags: cmd.Tags,
} }
if item.Epoch == 0 {
item.Epoch = time.Now().Unix()
}
if err := repo.Save(&item); err != nil { if err := repo.Save(&item); err != nil {
return ApiError(500, "Failed to save annotation", err) return ApiError(500, "Failed to save annotation", err)
} }
@@ -82,21 +98,22 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response
return ApiSuccess("Annotation added") return ApiSuccess("Annotation added")
} }
type GraphiteAnnotationError struct {
message string
}
func (e *GraphiteAnnotationError) Error() string {
return e.message
}
func formatGraphiteAnnotation(what string, data string) string { func formatGraphiteAnnotation(what string, data string) string {
return fmt.Sprintf("%s\n%s", what, data) text := what
if data != "" {
text = text + "\n" + data
}
return text
} }
func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotationsCmd) Response { func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotationsCmd) Response {
repo := annotations.GetRepository() repo := annotations.GetRepository()
if cmd.What == "" {
err := &CreateAnnotationError{"what field should not be empty"}
return ApiError(500, "Failed to save Graphite annotation", err)
}
if cmd.When == 0 { if cmd.When == 0 {
cmd.When = time.Now().Unix() cmd.When = time.Now().Unix()
} }
@@ -106,18 +123,22 @@ func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotati
var tagsArray []string var tagsArray []string
switch tags := cmd.Tags.(type) { switch tags := cmd.Tags.(type) {
case string: case string:
if tags != "" {
tagsArray = strings.Split(tags, " ") tagsArray = strings.Split(tags, " ")
} else {
tagsArray = []string{}
}
case []interface{}: case []interface{}:
for _, t := range tags { for _, t := range tags {
if tagStr, ok := t.(string); ok { if tagStr, ok := t.(string); ok {
tagsArray = append(tagsArray, tagStr) tagsArray = append(tagsArray, tagStr)
} else { } else {
err := &GraphiteAnnotationError{"tag should be a string"} err := &CreateAnnotationError{"tag should be a string"}
return ApiError(500, "Failed to save Graphite annotation", err) return ApiError(500, "Failed to save Graphite annotation", err)
} }
} }
default: default:
err := &GraphiteAnnotationError{"unsupported tags format"} err := &CreateAnnotationError{"unsupported tags format"}
return ApiError(500, "Failed to save Graphite annotation", err) return ApiError(500, "Failed to save Graphite annotation", err)
} }
@@ -133,7 +154,7 @@ func PostGraphiteAnnotation(c *middleware.Context, cmd dtos.PostGraphiteAnnotati
return ApiError(500, "Failed to save Graphite annotation", err) return ApiError(500, "Failed to save Graphite annotation", err)
} }
return ApiSuccess("Graphite Annotation added") return ApiSuccess("Graphite annotation added")
} }
func UpdateAnnotation(c *middleware.Context, cmd dtos.UpdateAnnotationsCmd) Response { func UpdateAnnotation(c *middleware.Context, cmd dtos.UpdateAnnotationsCmd) Response {

View File

@@ -188,9 +188,8 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
return return
} }
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
DisableCompression: true, ServeHTTP(ctx.Resp, ctx.Req.Request)
}).ServeHTTP(ctx.Resp, ctx.Req.Request)
} }
func (hs *HttpServer) healthHandler(ctx *macaron.Context) { func (hs *HttpServer) healthHandler(ctx *macaron.Context) {

View File

@@ -21,6 +21,10 @@ func Gziper() macaron.Handler {
return return
} }
if strings.HasPrefix(requestPath, "/metrics") {
return
}
ctx.Invoke(macaronGziper) ctx.Invoke(macaronGziper)
} }
} }

View File

@@ -18,6 +18,7 @@ const (
DS_KAIROSDB = "kairosdb" DS_KAIROSDB = "kairosdb"
DS_PROMETHEUS = "prometheus" DS_PROMETHEUS = "prometheus"
DS_POSTGRES = "postgres" DS_POSTGRES = "postgres"
DS_MYSQL = "mysql"
DS_ACCESS_DIRECT = "direct" DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy" DS_ACCESS_PROXY = "proxy"
) )
@@ -64,6 +65,7 @@ var knownDatasourcePlugins map[string]bool = map[string]bool{
DS_PROMETHEUS: true, DS_PROMETHEUS: true,
DS_OPENTSDB: true, DS_OPENTSDB: true,
DS_POSTGRES: true, DS_POSTGRES: true,
DS_MYSQL: true,
"opennms": true, "opennms": true,
"druid": true, "druid": true,
"dalmatinerdb": true, "dalmatinerdb": true,

View File

@@ -40,7 +40,7 @@ func getPluginLogoUrl(pluginType, path, baseUrl string) string {
} }
func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) { func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1) appSubPath := strings.Replace(strings.Replace(fp.PluginDir, app.PluginDir, "", 1), "\\", "/", 1)
fp.IncludedInAppId = app.Id fp.IncludedInAppId = app.Id
fp.BaseUrl = app.BaseUrl fp.BaseUrl = app.BaseUrl

View File

@@ -0,0 +1,34 @@
package plugins
import (
"testing"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestFrontendPlugin(t *testing.T) {
Convey("When setting paths based on App on Windows", t, func() {
setting.StaticRootPath = "c:\\grafana\\public"
fp := &FrontendPluginBase{
PluginBase: PluginBase{
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasource",
BaseUrl: "fpbase",
},
}
app := &AppPlugin{
FrontendPluginBase: FrontendPluginBase{
PluginBase: PluginBase{
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
Id: "testdata",
BaseUrl: "public/app/plugins/app/testdata",
},
},
}
fp.setPathsBasedOnApp(app)
So(fp.Module, ShouldEqual, "app/plugins/app/testdata/datasource/module")
})
}

View File

@@ -43,7 +43,7 @@ func (r *SqlAnnotationRepo) ensureTagsExist(sess *DBSession, tags []*models.Tag)
var existingTag models.Tag var existingTag models.Tag
// check if it exists // check if it exists
if exists, err := sess.Table("tag").Where("key=? AND value=?", tag.Key, tag.Value).Get(&existingTag); err != nil { if exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag); err != nil {
return nil, err return nil, err
} else if exists { } else if exists {
tag.Id = existingTag.Id tag.Id = existingTag.Id

View File

@@ -74,7 +74,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
if len(args) == 0 { if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name) return "", fmt.Errorf("missing time column argument for macro %v", name)
} }
return fmt.Sprintf("%s >= to_timestamp(%d) AND %s <= to_timestamp(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil return fmt.Sprintf("extract(epoch from %s) BETWEEN %d AND %d", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
case "__timeFrom": case "__timeFrom":
return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
case "__timeTo": case "__timeTo":
@@ -83,7 +83,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
if len(args) < 2 { if len(args) < 2 {
return "", fmt.Errorf("macro %v needs time column and interval", name) return "", fmt.Errorf("macro %v needs time column and interval", name)
} }
return fmt.Sprintf("(extract(epoch from \"%s\")/extract(epoch from %s::interval))::int", args[0], args[1]), nil return fmt.Sprintf("(extract(epoch from \"%s\")/extract(epoch from %s::interval))::int*extract(epoch from %s::interval)", args[0], args[1], args[1]), nil
case "__unixEpochFilter": case "__unixEpochFilter":
if len(args) == 0 { if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name) return "", fmt.Errorf("missing time column argument for macro %v", name)

View File

@@ -30,7 +30,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(timeRange, "WHERE $__timeFilter(time_column)") sql, err := engine.Interpolate(timeRange, "WHERE $__timeFilter(time_column)")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(sql, ShouldEqual, "WHERE time_column >= to_timestamp(18446744066914186738) AND time_column <= to_timestamp(18446744066914187038)") So(sql, ShouldEqual, "WHERE extract(epoch from time_column) BETWEEN 18446744066914186738 AND 18446744066914187038")
}) })
Convey("interpolate __timeFrom function", func() { Convey("interpolate __timeFrom function", func() {
@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')") sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/extract(epoch from '5m'::interval))::int") So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/extract(epoch from '5m'::interval))::int*extract(epoch from '5m'::interval)")
}) })
Convey("interpolate __timeTo function", func() { Convey("interpolate __timeTo function", func() {

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import coreModule from '../core_module'; import { react2AngularDirective } from 'app/core/utils/react2angular';
export interface IProps { export interface IProps {
password: string; password: string;
@@ -33,7 +33,5 @@ export class PasswordStrength extends React.Component<IProps, any> {
} }
} }
coreModule.directive('passwordStrength', function(reactDirective) { react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
return reactDirective(PasswordStrength, ['password']);
});

View File

@@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import coreModule from 'app/core/core_module';
import { sortedColors } from 'app/core/utils/colors'; import { sortedColors } from 'app/core/utils/colors';
export interface IProps { export interface IProps {
@@ -23,12 +22,15 @@ export class GfColorPalette extends React.Component<IProps, any> {
} }
render() { render() {
const colorPaletteItems = this.paletteColors.map((paletteColor) => { const colorPaletteItems = this.paletteColors.map(paletteColor => {
const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle'; const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle';
return ( return (
<i key={paletteColor} className={"pointer fa " + cssClass} <i
style={{'color': paletteColor}} key={paletteColor}
onClick={this.onColorSelect(paletteColor)}>&nbsp; className={'pointer fa ' + cssClass}
style={{ color: paletteColor }}
onClick={this.onColorSelect(paletteColor)}>
&nbsp;
</i> </i>
); );
}); });
@@ -40,6 +42,3 @@ export class GfColorPalette extends React.Component<IProps, any> {
} }
} }
coreModule.directive('gfColorPalette', function (reactDirective) {
return reactDirective(GfColorPalette, ['color', 'onColorSelect']);
});

View File

@@ -2,8 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import $ from 'jquery'; import $ from 'jquery';
import Drop from 'tether-drop'; import Drop from 'tether-drop';
import coreModule from 'app/core/core_module';
import { ColorPickerPopover } from './ColorPickerPopover'; import { ColorPickerPopover } from './ColorPickerPopover';
import { react2AngularDirective } from 'app/core/utils/react2angular';
export interface IProps { export interface IProps {
color: string; color: string;
@@ -27,9 +27,7 @@ export class ColorPicker extends React.Component<IProps, any> {
} }
openColorPicker() { openColorPicker() {
const dropContent = ( const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />;
<ColorPickerPopover color={this.props.color} onColorSelect={this.onColorSelect} />
);
let dropContentElem = document.createElement('div'); let dropContentElem = document.createElement('div');
ReactDOM.render(dropContent, dropContentElem); ReactDOM.render(dropContent, dropContentElem);
@@ -38,12 +36,12 @@ export class ColorPicker extends React.Component<IProps, any> {
target: this.pickerElem[0], target: this.pickerElem[0],
content: dropContentElem, content: dropContentElem,
position: 'top center', position: 'top center',
classes: 'drop-popover drop-popover--form', classes: 'drop-popover',
openOn: 'hover', openOn: 'click',
hoverCloseDelay: 200, hoverCloseDelay: 200,
tetherOptions: { tetherOptions: {
constraints: [{ to: 'scrollParent', attachment: "none both" }] constraints: [{ to: 'scrollParent', attachment: 'none both' }],
} },
}); });
drop.on('close', this.closeColorPicker); drop.on('close', this.closeColorPicker);
@@ -68,17 +66,14 @@ export class ColorPicker extends React.Component<IProps, any> {
return ( return (
<div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={this.setPickerElem}> <div className="sp-replacer sp-light" onClick={this.openColorPicker} ref={this.setPickerElem}>
<div className="sp-preview"> <div className="sp-preview">
<div className="sp-preview-inner" style={{backgroundColor: this.props.color}}> <div className="sp-preview-inner" style={{ backgroundColor: this.props.color }} />
</div>
</div> </div>
</div> </div>
); );
} }
} }
coreModule.directive('colorPicker', function (reactDirective) { react2AngularDirective('colorPicker', ColorPicker, [
return reactDirective(ColorPicker, [
'color', 'color',
['onChange', { watchDepth: 'reference', wrapApply: true }] ['onChange', { watchDepth: 'reference', wrapApply: true }],
]); ]);
});

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import $ from 'jquery'; import $ from 'jquery';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import coreModule from 'app/core/core_module';
import { GfColorPalette } from './ColorPalette'; import { GfColorPalette } from './ColorPalette';
import { GfSpectrumPicker } from './SpectrumPicker'; import { GfSpectrumPicker } from './SpectrumPicker';
@@ -115,7 +114,3 @@ export class ColorPickerPopover extends React.Component<IProps, any> {
); );
} }
} }
coreModule.directive('gfColorPickerPopover', function (reactDirective) {
return reactDirective(ColorPickerPopover, ['color', 'onColorSelect']);
});

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import coreModule from 'app/core/core_module'; import { ColorPickerPopover } from './ColorPickerPopover';
import {ColorPickerPopover} from './ColorPickerPopover'; import { react2AngularDirective } from 'app/core/utils/react2angular';
export interface IProps { export interface IProps {
series: any; series: any;
@@ -50,6 +50,4 @@ export class SeriesColorPicker extends React.Component<IProps, any> {
} }
} }
coreModule.directive('seriesColorPicker', function(reactDirective) { react2AngularDirective('seriesColorPicker', SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);
return reactDirective(SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);
});

View File

@@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import coreModule from 'app/core/core_module';
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import 'vendor/spectrum'; import 'vendor/spectrum';
@@ -71,6 +70,3 @@ export class GfSpectrumPicker extends React.Component<IProps, any> {
} }
} }
coreModule.directive('gfSpectrumPicker', function (reactDirective) {
return reactDirective(GfSpectrumPicker, ['color', 'options', 'onColorSelect']);
});

View File

@@ -5,6 +5,7 @@
*/ */
import coreModule from '../../core_module'; import coreModule from '../../core_module';
/** @ngInject */
export function spectrumPicker() { export function spectrumPicker() {
return { return {
restrict: 'E', restrict: 'E',

View File

@@ -1,7 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import {saveAs} from 'file-saver';
declare var window: any;
const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ'; const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
@@ -69,5 +68,5 @@ export function exportTableDataToCsv(table, excel = false) {
export function saveSaveBlob(payload, fname) { export function saveSaveBlob(payload, fname) {
var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); var blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
window.saveAs(blob, fname); saveAs(blob, fname);
} }

View File

@@ -0,0 +1,10 @@
import coreModule from 'app/core/core_module';
export function react2AngularDirective(name: string, component: any, options: any) {
coreModule.directive(name, ['reactDirective', reactDirective => {
return reactDirective(component, options);
}]);
}

View File

@@ -383,6 +383,7 @@ export class AlertTabCtrl {
test() { test() {
this.testing = true; this.testing = true;
this.testResult = false;
var payload = { var payload = {
dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(), dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),

View File

@@ -1,8 +1,7 @@
///<reference path="../../../headers/common.d.ts" />
import angular from 'angular'; import angular from 'angular';
import coreModule from 'app/core/core_module'; import {saveAs} from 'file-saver';
import coreModule from 'app/core/core_module';
import {DashboardExporter} from './exporter'; import {DashboardExporter} from './exporter';
export class DashExportCtrl { export class DashExportCtrl {
@@ -22,9 +21,8 @@ export class DashExportCtrl {
} }
save() { save() {
var blob = new Blob([angular.toJson(this.dash, true)], { type: "application/json;charset=utf-8" }); var blob = new Blob([angular.toJson(this.dash, true)], {type: 'application/json;charset=utf-8'});
var wnd: any = window; saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json');
wnd.saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json');
} }
saveJson() { saveJson() {
@@ -44,7 +42,7 @@ export function dashExportDirective() {
controller: DashExportCtrl, controller: DashExportCtrl,
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: {dismiss: "&"} scope: {dismiss: '&'},
}; };
} }

View File

@@ -9,11 +9,23 @@ import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import TableModel from 'app/core/table_model'; import TableModel from 'app/core/table_model';
import {coreModule, appEvents, contextSrv} from 'app/core/core'; import {coreModule, appEvents, contextSrv} from 'app/core/core';
import * as datemath from 'app/core/utils/datemath';
import * as fileExport from 'app/core/utils/file_export';
import * as flatten from 'app/core/utils/flatten';
import * as ticks from 'app/core/utils/ticks';
import {impressions} from 'app/features/dashboard/impression_store';
import builtInPlugins from './built_in_plugins';
import d3 from 'vendor/d3/d3';
// rxjs
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject'; import {Subject} from 'rxjs/Subject';
import * as datemath from 'app/core/utils/datemath';
import builtInPlugins from './buit_in_plugins'; // these imports add functions to Observable
import d3 from 'vendor/d3/d3'; import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/combineAll';
System.config({ System.config({
baseURL: 'public', baseURL: 'public',
@@ -27,6 +39,9 @@ System.config({
text: 'vendor/plugin-text/text.js', text: 'vendor/plugin-text/text.js',
css: 'vendor/plugin-css/css.js' css: 'vendor/plugin-css/css.js'
}, },
meta: {
'*': {esModule: true}
}
}); });
// add cache busting // add cache busting
@@ -53,20 +68,30 @@ exposeToPlugin('rxjs/Subject', Subject);
exposeToPlugin('rxjs/Observable', Observable); exposeToPlugin('rxjs/Observable', Observable);
exposeToPlugin('d3', d3); exposeToPlugin('d3', d3);
exposeToPlugin('app/features/dashboard/impression_store', {
impressions: impressions,
__esModule: true
});
exposeToPlugin('app/plugins/sdk', sdk); exposeToPlugin('app/plugins/sdk', sdk);
exposeToPlugin('app/core/utils/datemath', datemath); exposeToPlugin('app/core/utils/datemath', datemath);
exposeToPlugin('app/core/utils/file_export', fileExport);
exposeToPlugin('app/core/utils/flatten', flatten);
exposeToPlugin('app/core/utils/kbn', kbn); exposeToPlugin('app/core/utils/kbn', kbn);
exposeToPlugin('app/core/utils/ticks', ticks);
exposeToPlugin('app/core/config', config); exposeToPlugin('app/core/config', config);
exposeToPlugin('app/core/time_series', TimeSeries); exposeToPlugin('app/core/time_series', TimeSeries);
exposeToPlugin('app/core/time_series2', TimeSeries); exposeToPlugin('app/core/time_series2', TimeSeries);
exposeToPlugin('app/core/table_model', TableModel); exposeToPlugin('app/core/table_model', TableModel);
exposeToPlugin('app/core/app_events', appEvents); exposeToPlugin('app/core/app_events', appEvents);
exposeToPlugin('app/core/core_module', coreModule); exposeToPlugin('app/core/core_module', coreModule);
exposeToPlugin('app/core/core_module', coreModule);
exposeToPlugin('app/core/core', { exposeToPlugin('app/core/core', {
coreModule: coreModule, coreModule: coreModule,
appEvents: appEvents, appEvents: appEvents,
contextSrv: contextSrv, contextSrv: contextSrv,
__esModule: true
}); });
import 'vendor/flot/jquery.flot'; import 'vendor/flot/jquery.flot';
@@ -79,7 +104,11 @@ import 'vendor/flot/jquery.flot.fillbelow';
import 'vendor/flot/jquery.flot.crosshair'; import 'vendor/flot/jquery.flot.crosshair';
import 'vendor/flot/jquery.flot.dashes'; import 'vendor/flot/jquery.flot.dashes';
for (let flotDep of ['jquery.flot', 'jquery.flot.pie', 'jquery.flot.time']) { const flotDeps = [
'jquery.flot', 'jquery.flot.pie', 'jquery.flot.time', 'jquery.flot.fillbelow', 'jquery.flot.crosshair',
'jquery.flot.stack', 'jquery.flot.selection', 'jquery.flot.stackpercent', 'jquery.flot.events'
];
for (let flotDep of flotDeps) {
exposeToPlugin(flotDep, {fakeDep: 1}); exposeToPlugin(flotDep, {fakeDep: 1});
} }

View File

@@ -41,7 +41,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
item.namespace = templateSrv.replace(item.namespace, options.scopedVars); item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
item.metricName = templateSrv.replace(item.metricName, options.scopedVars); item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars); item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
item.period = self.getPeriod(item, options); item.period = String(self.getPeriod(item, options)); // use string format for period in graph query, and alerting
return _.extend({ return _.extend({
refId: item.refId, refId: item.refId,
@@ -318,6 +318,8 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () { return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
return { status: 'success', message: 'Data source is working' }; return { status: 'success', message: 'Data source is working' };
}, function (err) {
return { status: 'error', message: err.message };
}); });
}; };
@@ -352,6 +354,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
var t = angular.copy(target); var t = angular.copy(target);
var scopedVar = {}; var scopedVar = {};
scopedVar[variable.name] = v; scopedVar[variable.name] = v;
t.refId = target.refId + '_' + v.value;
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar); t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
return t; return t;
}).value(); }).value();

View File

@@ -37,7 +37,7 @@ describe('CloudWatchDatasource', function() {
InstanceId: 'i-12345678' InstanceId: 'i-12345678'
}, },
statistics: ['Average'], statistics: ['Average'],
period: 300 period: '300'
} }
] ]
}; };
@@ -109,7 +109,7 @@ describe('CloudWatchDatasource', function() {
ctx.ds.query(query).then(function() { ctx.ds.query(query).then(function() {
var params = requestParams.queries[0]; var params = requestParams.queries[0];
expect(params.period).to.be(600); expect(params.period).to.be('600');
done(); done();
}); });
ctx.$rootScope.$apply(); ctx.$rootScope.$apply();

View File

@@ -75,6 +75,12 @@ export class ElasticDatasource {
return this.request('POST', url, data).then(function(results) { return this.request('POST', url, data).then(function(results) {
results.data.$$config = results.config; results.data.$$config = results.config;
return results.data; return results.data;
}).catch(err => {
if (err.data && err.data.error) {
throw {message: 'Elasticsearch error: ' + err.data.error.reason, error: err.data.error};
}
throw err;
}); });
} }

View File

@@ -20,6 +20,10 @@ export class PostgresDatasource {
return '\'' + value + '\''; return '\'' + value + '\'';
} }
if (typeof value === 'number') {
return value.toString();
}
var quotedValues = _.map(value, function(val) { var quotedValues = _.map(value, function(val) {
return '\'' + val + '\''; return '\'' + val + '\'';
}); });

View File

@@ -16,8 +16,8 @@ class PostgresConfigCtrl {
const defaultQuery = `SELECT const defaultQuery = `SELECT
extract(epoch from time_column) AS time, extract(epoch from time_column) AS time,
title_column as title, text_column as text,
description_column as text tags_column as tags
FROM FROM
metric_table metric_table
WHERE WHERE

View File

@@ -8,7 +8,7 @@ export class PromCompleter {
labelNameCache: any; labelNameCache: any;
labelValueCache: any; labelValueCache: any;
identifierRegexps = [/[\[\]a-zA-Z_0-9=]/]; identifierRegexps = [/\[/, /[a-zA-Z0-9_:]/];
constructor(private datasource: PrometheusDatasource) { constructor(private datasource: PrometheusDatasource) {
this.labelQueryCache = {}; this.labelQueryCache = {};
@@ -73,13 +73,15 @@ export class PromCompleter {
}); });
} }
if (prefix === '[') { if (token.type === 'paren.lparen' && token.value === '[') {
var vectors = []; var vectors = [];
for (let unit of ['s', 'm', 'h']) { for (let unit of ['s', 'm', 'h']) {
for (let value of [1,5,10,30]) { for (let value of [1,5,10,30]) {
vectors.push({caption: value+unit, value: '['+value+unit, meta: 'range vector'}); vectors.push({caption: value+unit, value: '['+value+unit, meta: 'range vector'});
} }
} }
vectors.push({caption: '$__interval', value: '[$__interval', meta: 'range vector'});
vectors.push({caption: '$__interval_ms', value: '[$__interval_ms', meta: 'range vector'});
callback(null, vectors); callback(null, vectors);
return; return;
} }

View File

@@ -168,7 +168,7 @@ export class PrometheusDatasource {
if (interval !== 0 && range / intervalFactor / interval > 11000) { if (interval !== 0 && range / intervalFactor / interval > 11000) {
interval = Math.ceil(range / intervalFactor / 11000); interval = Math.ceil(range / intervalFactor / 11000);
} }
return Math.max(interval * intervalFactor, minInterval); return Math.max(interval * intervalFactor, minInterval, 1);
} }
performTimeSeriesQuery(query, start, end) { performTimeSeriesQuery(query, start, end) {

View File

@@ -44,12 +44,18 @@ describe('Prometheus editor completer', function() {
describe('When inside brackets', () => { describe('When inside brackets', () => {
it('Should return range vectors', () => { it('Should return range vectors', () => {
const session = getSessionStub({ const session = getSessionStub({
currentToken: {}, currentToken: {type: 'paren.lparen', value: '[', index: 2, start: 9},
tokens: [], tokens: [
line: '', {type: 'identifier', value: 'node_cpu'},
{type: 'paren.lparen', value: '['}
],
line: 'node_cpu[',
}); });
completer.getCompletions(editor, session, {row: 0, column: 10}, '[', (s, res) => {
expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'}); return completer.getCompletions(editor, session, {row: 0, column: 10}, '[', (s, res) => {
expect(res[0].caption).to.eql('1s');
expect(res[0].value).to.eql('[1s');
expect(res[0].meta).to.eql('range vector');
}); });
}); });
}); });

View File

@@ -294,6 +294,20 @@ describe('PrometheusDatasource', function() {
ctx.ds.query(query); ctx.ds.query(query);
ctx.$httpBackend.verifyNoOutstandingExpectation(); ctx.$httpBackend.verifyNoOutstandingExpectation();
}); });
it('step should never go below 1', function() {
var query = {
// 6 hour range
range: { from: moment(1508318768202), to: moment(1508318770118) },
targets: [{expr: 'test'}],
interval: '100ms'
};
var urlExpected = 'proxied/api/v1/query_range?query=test&start=1508318769&end=1508318771&step=1';
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query);
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
it('should be auto interval when greater than min interval', function() { it('should be auto interval when greater than min interval', function() {
var query = { var query = {
// 6 hour range // 6 hour range

View File

@@ -34,7 +34,7 @@ class GettingStartedPanelCtrl extends PanelCtrl {
check: () => { check: () => {
return $q.when( return $q.when(
datasourceSrv.getMetricSources().filter(item => { datasourceSrv.getMetricSources().filter(item => {
return item.meta.builtIn === false; return item.meta.builtIn !== true;
}).length > 0 }).length > 0
); );
} }

View File

@@ -152,7 +152,7 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
.tickSize(2); .tickSize(2);
let colorRect = legendElem.find(":first-child"); let colorRect = legendElem.find(":first-child");
let posY = colorRect.height() + 2; let posY = getSvgElemHeight(legendElem) + 2;
let posX = getSvgElemX(colorRect); let posX = getSvgElemX(colorRect);
d3.select(legendElem.get(0)).append("g") d3.select(legendElem.get(0)).append("g")
@@ -256,7 +256,16 @@ function getOpacityScale(options, maxValue, minValue = 0) {
function getSvgElemX(elem) { function getSvgElemX(elem) {
let svgElem = elem.get(0); let svgElem = elem.get(0);
if (svgElem && svgElem.x && svgElem.x.baseVal) { if (svgElem && svgElem.x && svgElem.x.baseVal) {
return elem.get(0).x.baseVal.value; return svgElem.x.baseVal.value;
} else {
return 0;
}
}
function getSvgElemHeight(elem) {
let svgElem = elem.get(0);
if (svgElem && svgElem.height && svgElem.height.baseVal) {
return svgElem.height.baseVal.value;
} else { } else {
return 0; return 0;
} }

View File

@@ -71,9 +71,8 @@ export default function link(scope, elem, attrs, ctrl) {
function getYAxisWidth(elem) { function getYAxisWidth(elem) {
let axis_text = elem.selectAll(".axis-y text").nodes(); let axis_text = elem.selectAll(".axis-y text").nodes();
let max_text_width = _.max(_.map(axis_text, text => { let max_text_width = _.max(_.map(axis_text, text => {
let el = $(text); // Use SVG getBBox method
// Use JQuery outerWidth() to compute full element width return text.getBBox().width;
return el.outerWidth();
})); }));
return max_text_width; return max_text_width;

View File

@@ -27,8 +27,8 @@ $white: #fff;
// ------------------------- // -------------------------
$blue: #33B5E5; $blue: #33B5E5;
$blue-dark: #005f81; $blue-dark: #005f81;
$green: #609000; $green: #299c46;
$red: #CC3900; $red: #d44a3a;
$yellow: #ECBB13; $yellow: #ECBB13;
$pink: #FF4444; $pink: #FF4444;
$purple: #9933CC; $purple: #9933CC;
@@ -130,20 +130,20 @@ $table-border: $dark-3; // table and cell border
// Buttons // Buttons
// ------------------------- // -------------------------
$btn-primary-bg: $brand-primary; $btn-primary-bg: #ff6600;
$btn-primary-bg-hl: lighten($brand-primary, 8%); $btn-primary-bg-hl: #bc3e06;
$btn-secondary-bg: $blue-dark; $btn-secondary-bg: $blue-dark;
$btn-secondary-bg-hl: lighten($blue-dark, 5%); $btn-secondary-bg-hl: lighten($blue-dark, 5%);
$btn-success-bg: lighten($green, 3%); $btn-success-bg: $green;
$btn-success-bg-hl: darken($green, 3%); $btn-success-bg-hl: darken($green, 6%);
$btn-warning-bg: $brand-warning; $btn-warning-bg: $brand-warning;
$btn-warning-bg-hl: lighten($brand-warning, 8%); $btn-warning-bg-hl: lighten($brand-warning, 8%);
$btn-danger-bg: $red; $btn-danger-bg: $red;
$btn-danger-bg-hl: lighten($red, 5%); $btn-danger-bg-hl: darken($red, 8%);
$btn-inverse-bg: $dark-3; $btn-inverse-bg: $dark-3;
$btn-inverse-bg-hl: lighten($dark-3, 4%); $btn-inverse-bg-hl: lighten($dark-3, 4%);

View File

@@ -32,8 +32,8 @@ $white: #fff;
// ------------------------- // -------------------------
$blue: #2AB2E4; $blue: #2AB2E4;
$blue-dark: #3CAAD6; $blue-dark: #3CAAD6;
$green: #28B62C; $green: #3aa655;
$red: #FF4136; $red: #d44939;
$yellow: #FF851B; $yellow: #FF851B;
$orange: #Ff7941; $orange: #Ff7941;
$pink: #E671B8; $pink: #E671B8;

View File

@@ -121,6 +121,7 @@ $gf-form-margin: 0.25rem;
// text areas should be scrollable // text areas should be scrollable
@at-root textarea#{&} { @at-root textarea#{&} {
overflow: auto; overflow: auto;
white-space: pre-wrap;
} }
// Unstyle the caret on `<select>`s in IE10+. // Unstyle the caret on `<select>`s in IE10+.

View File

@@ -211,15 +211,6 @@
margin-right: 0px; margin-right: 0px;
line-height: initial; line-height: initial;
} }
.close {
margin-right: 5px;
color: $link-color;
opacity: 0.7;
text-shadow: none;
}
.editor-row {
padding: 5px;
}
} }
.annotation-tags { .annotation-tags {