From 7db848f153f083b5efafc4c3bae177eeafb99f8b Mon Sep 17 00:00:00 2001 From: bugficks Date: Tue, 15 Jan 2019 13:29:56 +0100 Subject: [PATCH 01/50] [Feature request] MySQL SSL CA in datasource connector https://github.com/grafana/grafana/issues/8570 --- pkg/tsdb/mysql/mysql.go | 44 ++++++++++++ .../datasource/mysql/partials/config.html | 68 ++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 35b03e489a0..e713b87e265 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -6,6 +6,10 @@ import ( "reflect" "strconv" "strings" + "errors" + + "crypto/x509" + "crypto/tls" "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" @@ -32,6 +36,46 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin datasource.Url, datasource.Database, ) + + var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool + if datasource.JsonData != nil { + tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false) + tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false) + tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false) + } + + if tlsAuth || tlsAuthWithCACert { + + secureJsonData := datasource.SecureJsonData.Decrypt() + tlsConfig := tls.Config { + InsecureSkipVerify: tlsSkipVerify, + } + + if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 { + + caPool := x509.NewCertPool() + if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok { + return nil, errors.New("Failed to parse TLS CA PEM certificate") + } + + tlsConfig.RootCAs = caPool + } + + if tlsAuth { + certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"])) + if err != nil { + return nil, err + } + clientCert := make([]tls.Certificate, 0, 1) + clientCert = append(clientCert, certs) + + tlsConfig.Certificates = clientCert + } + + mysql.RegisterTLSConfig(datasource.Name, &tlsConfig) + cnnstr += "&tls=" + datasource.Name + } + logger.Debug("getEngine", "connection", cnnstr) config := tsdb.SqlQueryEndpointConfiguration{ diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html index a35633c626a..5f3ba5c1286 100644 --- a/public/app/plugins/datasource/mysql/partials/config.html +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -1,4 +1,3 @@ -

MySQL Connection

@@ -22,6 +21,72 @@
+ +
+
+ + +
+
+ +
+
+ +
+
+
TLS Auth Details
+ TLS Certs are encrypted and stored in the Grafana database. +
+
+
+
+ +
+
+ +
+ +
+ + reset +
+
+
+ +
+
+
+ +
+
+ +
+
+ + reset +
+
+ +
+
+ +
+
+ +
+
+ + reset +
+
+
+
Connection limits @@ -84,4 +149,3 @@

- From f31fe495e977cd9fe1c585e25221a423ff9a7c71 Mon Sep 17 00:00:00 2001 From: bugficks Date: Tue, 15 Jan 2019 13:54:25 +0100 Subject: [PATCH 02/50] fix go fmt --- pkg/tsdb/mysql/mysql.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index e713b87e265..82e7cac27f0 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -2,14 +2,14 @@ package mysql import ( "database/sql" + "errors" "fmt" "reflect" "strconv" "strings" - "errors" - "crypto/x509" "crypto/tls" + "crypto/x509" "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" @@ -47,7 +47,7 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin if tlsAuth || tlsAuthWithCACert { secureJsonData := datasource.SecureJsonData.Decrypt() - tlsConfig := tls.Config { + tlsConfig := tls.Config{ InsecureSkipVerify: tlsSkipVerify, } From 7df5e3cebf06b39c0007bca76c9e86254fc7bc5a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 28 Jan 2019 19:37:19 +0100 Subject: [PATCH 03/50] extract tls auth settings directive from datasource http settings directive --- public/app/features/all.ts | 1 + .../datasources/partials/http_settings.html | 52 +------------- .../partials/tls_auth_settings.html | 62 +++++++++++++++++ .../settings/TlsAuthSettingsCtrl.ts | 10 +++ .../datasource/mysql/partials/config.html | 68 +++---------------- 5 files changed, 84 insertions(+), 109 deletions(-) create mode 100644 public/app/features/datasources/partials/tls_auth_settings.html create mode 100644 public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts diff --git a/public/app/features/all.ts b/public/app/features/all.ts index 83146596ea0..d5e684e4a4e 100644 --- a/public/app/features/all.ts +++ b/public/app/features/all.ts @@ -12,3 +12,4 @@ import './manage-dashboards'; import './teams/CreateTeamCtrl'; import './profile/all'; import './datasources/settings/HttpSettingsCtrl'; +import './datasources/settings/TlsAuthSettingsCtrl'; diff --git a/public/app/features/datasources/partials/http_settings.html b/public/app/features/datasources/partials/http_settings.html index 521e2d3cdc6..b6f2c4fc0dd 100644 --- a/public/app/features/datasources/partials/http_settings.html +++ b/public/app/features/datasources/partials/http_settings.html @@ -101,53 +101,5 @@ -
-
-
TLS Auth Details
- TLS Certs are encrypted and stored in the Grafana database. -
-
-
-
- -
-
- -
- -
- - reset -
-
-
- -
-
-
- -
-
- -
-
- - reset -
-
- -
-
- -
-
- -
-
- - reset -
-
-
-
- + + \ No newline at end of file diff --git a/public/app/features/datasources/partials/tls_auth_settings.html b/public/app/features/datasources/partials/tls_auth_settings.html new file mode 100644 index 00000000000..c852e8ec70c --- /dev/null +++ b/public/app/features/datasources/partials/tls_auth_settings.html @@ -0,0 +1,62 @@ +
+
+
TLS Auth Details
+ TLS Certs are encrypted and stored in the Grafana database. +
+
+
+
+
+ +
+ +
+ + reset +
+
+
+ +
+
+
+
+ +
+
+ + reset +
+
+ +
+
+
+ +
+
+ + reset +
+
+
+
diff --git a/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts new file mode 100644 index 00000000000..7c21fab404c --- /dev/null +++ b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts @@ -0,0 +1,10 @@ +import { coreModule } from 'app/core/core'; + +coreModule.directive('datasourceTlsAuthSettings', () => { + return { + scope: { + current: '=', + }, + templateUrl: 'public/app/features/datasources/partials/tls_auth_settings.html', + }; +}); diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html index 5f3ba5c1286..8221a06e1ee 100644 --- a/public/app/plugins/datasource/mysql/partials/config.html +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -24,70 +24,20 @@
- - + +
- +
-
-
-
TLS Auth Details
- TLS Certs are encrypted and stored in the Grafana database. -
-
-
-
- -
-
- -
- -
- - reset -
-
-
- -
-
-
- -
-
- -
-
- - reset -
-
- -
-
- -
-
- -
-
- - reset -
-
-
-
- + + Connection limits From f157c19e16cdc970542867ec77eb5a61fe5f11ad Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 28 Jan 2019 19:38:56 +0100 Subject: [PATCH 04/50] extract parsing of datasource tls config to method --- pkg/models/datasource_cache.go | 48 +++++++++++++++++++++------------- pkg/tsdb/mysql/mysql.go | 43 ++++-------------------------- 2 files changed, 35 insertions(+), 56 deletions(-) diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index 66ba66e4d39..1c895514ace 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -46,19 +46,16 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { return t.Transport, nil } - var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool - if ds.JsonData != nil { - tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false) - tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) - tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false) + tlsConfig, err := ds.GetTLSConfig() + if err != nil { + return nil, err } + tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient + transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: tlsSkipVerify, - Renegotiation: tls.RenegotiateFreelyAsClient, - }, - Proxy: http.ProxyFromEnvironment, + TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, @@ -70,6 +67,26 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { IdleConnTimeout: 90 * time.Second, } + ptc.cache[ds.Id] = cachedTransport{ + Transport: transport, + updated: ds.Updated, + } + + return transport, nil +} + +func (ds *DataSource) GetTLSConfig() (*tls.Config, error) { + var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool + if ds.JsonData != nil { + tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false) + tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) + tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false) + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: tlsSkipVerify, + } + if tlsClientAuth || tlsAuthWithCACert { decrypted := ds.SecureJsonData.Decrypt() if tlsAuthWithCACert && len(decrypted["tlsCACert"]) > 0 { @@ -78,7 +95,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { if !ok { return nil, errors.New("Failed to parse TLS CA PEM certificate") } - transport.TLSClientConfig.RootCAs = caPool + tlsConfig.RootCAs = caPool } if tlsClientAuth { @@ -86,14 +103,9 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { if err != nil { return nil, err } - transport.TLSClientConfig.Certificates = []tls.Certificate{cert} + tlsConfig.Certificates = []tls.Certificate{cert} } } - ptc.cache[ds.Id] = cachedTransport{ - Transport: transport, - updated: ds.Updated, - } - - return transport, nil + return tlsConfig, nil } diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 82e7cac27f0..d451150f1de 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -2,15 +2,11 @@ package mysql import ( "database/sql" - "errors" "fmt" "reflect" "strconv" "strings" - "crypto/tls" - "crypto/x509" - "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" "github.com/grafana/grafana/pkg/log" @@ -37,42 +33,13 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin datasource.Database, ) - var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool - if datasource.JsonData != nil { - tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false) - tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false) - tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false) + tlsConfig, err := datasource.GetTLSConfig() + if err != nil { + return nil, err } - if tlsAuth || tlsAuthWithCACert { - - secureJsonData := datasource.SecureJsonData.Decrypt() - tlsConfig := tls.Config{ - InsecureSkipVerify: tlsSkipVerify, - } - - if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 { - - caPool := x509.NewCertPool() - if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok { - return nil, errors.New("Failed to parse TLS CA PEM certificate") - } - - tlsConfig.RootCAs = caPool - } - - if tlsAuth { - certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"])) - if err != nil { - return nil, err - } - clientCert := make([]tls.Certificate, 0, 1) - clientCert = append(clientCert, certs) - - tlsConfig.Certificates = clientCert - } - - mysql.RegisterTLSConfig(datasource.Name, &tlsConfig) + if tlsConfig.RootCAs != nil || len(tlsConfig.Certificates) > 0 { + mysql.RegisterTLSConfig(datasource.Name, tlsConfig) cnnstr += "&tls=" + datasource.Name } From 1f3fafb198fc47e143e81afaa5bf497e1059f401 Mon Sep 17 00:00:00 2001 From: Paresh Date: Sun, 3 Feb 2019 13:07:33 -0600 Subject: [PATCH 05/50] mssql: pass timerange for template variable queries --- .../app/plugins/datasource/mssql/datasource.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 23aa5504d3e..1ede9cc3d1e 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -107,13 +107,24 @@ export class MssqlDatasource { format: 'table', }; + const data = { + queries: [interpolatedQuery], + }; + + if (optionalOptions && optionalOptions.range) { + if (optionalOptions.range.from) { + data['from'] = optionalOptions.range.from.valueOf().toString(); + } + if (optionalOptions.range.to) { + data['to'] = optionalOptions.range.to.valueOf().toString(); + } + } + return this.backendSrv .datasourceRequest({ url: '/api/tsdb/query', method: 'POST', - data: { - queries: [interpolatedQuery], - }, + data: data, }) .then(data => this.responseParser.parseMetricFindQueryResult(refId, data)); } From 7626ce9922ccac4b15f82853eef8439ab37d78a7 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 4 Feb 2019 17:28:57 +0100 Subject: [PATCH 06/50] WIP Enable js defined theme to be used in SASS --- package.json | 1 + packages/grafana-ui/src/index.ts | 1 + packages/grafana-ui/src/theme.d.ts | 116 ++++++++++++++++++++++ packages/grafana-ui/src/theme.js | 15 +++ packages/grafana-ui/src/themes/dark.js | 64 ++++++++++++ packages/grafana-ui/src/themes/default.js | 47 +++++++++ packages/grafana-ui/src/themes/light.js | 65 ++++++++++++ public/sass/_variables.dark.scss | 101 +++++++++---------- public/sass/_variables.light.scss | 94 +++++++++--------- public/sass/_variables.scss | 44 ++++---- scripts/webpack/getThemeVariable.js | 53 ++++++++++ scripts/webpack/sass.rule.js | 13 ++- scripts/webpack/webpack.hot.js | 10 +- yarn.lock | 5 + 14 files changed, 506 insertions(+), 123 deletions(-) create mode 100644 packages/grafana-ui/src/theme.d.ts create mode 100644 packages/grafana-ui/src/theme.js create mode 100644 packages/grafana-ui/src/themes/dark.js create mode 100644 packages/grafana-ui/src/themes/default.js create mode 100644 packages/grafana-ui/src/themes/light.js create mode 100644 scripts/webpack/getThemeVariable.js diff --git a/package.json b/package.json index 77fd92baf57..60a8a20cde3 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "ng-annotate-webpack-plugin": "^0.3.0", "ngtemplate-loader": "^2.0.1", "node-sass": "^4.11.0", + "node-sass-utils": "^1.1.2", "npm": "^5.4.2", "optimize-css-assets-webpack-plugin": "^4.0.2", "phantomjs-prebuilt": "^2.1.15", diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index 974d976bbef..4ddc7c8485a 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,3 +1,4 @@ export * from './components'; export * from './types'; export * from './utils'; +export * from './theme'; diff --git a/packages/grafana-ui/src/theme.d.ts b/packages/grafana-ui/src/theme.d.ts new file mode 100644 index 00000000000..015bcd16136 --- /dev/null +++ b/packages/grafana-ui/src/theme.d.ts @@ -0,0 +1,116 @@ +export interface GrafanaThemeType { + name: string; + // TODO: not sure if should be a part of theme + brakpoints: { + xs: string; + s: string; + m: string; + l: string; + xl: string; + }; + typography: { + fontFamily: { + sansSerif: string; + serif: string; + monospace: string; + }; + size: { + base: string; + xs: string; + s: string; + m: string; + l: string; + }; + weight: { + light: number; + normal: number; + semibold: number; + }; + lineHeight: { + xs: number; //1 + s: number; //1.1 + m: number; // 4/3 + l: number; // 1.5 + }; + // TODO: Refactor to use size instead of custom defs + heading: { + h1: string; + h2: string; + h3: string; + h4: string; + h5: string; + h6: string; + }; + }; + spacing: { + xs: string; + s: string; + m: string; + l: string; + gutter: string; + }; + border: { + radius: { + xs: string; + s: string; + m: string; + }; + }; + colors: { + black: string; + white: string; + dark1: string; + dark2: string; + dark3: string; + dark4: string; + dark5: string; + gray1: string; + gray2: string; + gray3: string; + gray4: string; + gray5: string; + gray6: string; + gray7: string; + grayBlue: string; + inputBlack: string; + + // Accent colors + blue: string; + blueLight: string; + blueDark: string; + green: string; + red: string; + yellow: string; + pink: string; + purple: string; + variable: string; + orange: string; + queryRed: string; + queryGreen: string; + queryPurple: string; + queryKeyword: string; + queryOrange: string; + + // Status colors + online: string; + warn: string; + critical: string; + + // TODO: should this be a part of theme? + bodyBg: string; + pageBg: string; + bodyColor: string; + textColor: string; + textColorStrong: string; + textColorWeak: string; + textColorFaint: string; + textColorEmphasis: string; + linkColor: string; + linkColorDisabled: string; + linkColorHover: string; + linkColorExternal: string; + headingColor: string; + }; +} +export function getTheme(): GrafanaThemeType +export function mockTheme(themeMock: Partial): () => void diff --git a/packages/grafana-ui/src/theme.js b/packages/grafana-ui/src/theme.js new file mode 100644 index 00000000000..3d0695e2490 --- /dev/null +++ b/packages/grafana-ui/src/theme.js @@ -0,0 +1,15 @@ +const darkTheme = require('./themes/dark'); +const lightTheme = require('./themes/light'); + +const getTheme = name => (name === 'light' ? lightTheme : darkTheme); + +const mockTheme = mock => { + const originalGetTheme = getTheme; + getTheme = () => mock; + return () => (getTheme = originalGetTheme); +}; + +module.exports = { + getTheme, + mockTheme, +}; diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.js new file mode 100644 index 00000000000..c80a4593f53 --- /dev/null +++ b/packages/grafana-ui/src/themes/dark.js @@ -0,0 +1,64 @@ + + +const defaultTheme = require('./default'); +const tinycolor = require('tinycolor2'); + +const basicColors = { + black: '#00ff00', + white: '#ffffff', + dark1: '#141414', + dark2: '#1f1f20', + dark3: '#262628', + dark4: '#333333', + dark5: '#444444', + gray1: '#555555', + gray2: '#8e8e8e', + gray3: '#b3b3b3', + gray4: '#d8d9da', + gray5: '#ececec', + gray6: '#f4f5f8', + gray7: '#fbfbfb', + grayBlue: '#212327', + blue: '#33b5e5', + blueDark: '#005f81', + blueLight: '#00a8e6', // not used in dark theme + green: '#299c46', + red: '#d44a3a', + yellow: '#ecbb13', + pink: '#ff4444', + purple: '#9933cc', + variable: '#32d1df', + orange: '#eb7b18', +}; + +const darkTheme = { + ...defaultTheme, + name: 'Grafana Dark', + colors: { + ...basicColors, + inputBlack: '#09090b', + queryRed: '#e24d42', + queryGreen: '#74e680', + queryPurple: '#fe85fc', + queryKeyword: '#66d9ef', + queryOrange: 'eb7b18', + online: '#10a345', + warn: '#f79520', + critical: '#ed2e18', + bodyBg: '#171819', + pageBg: '#161719', + bodyColor: basicColors.gray4, + textColor: basicColors.gray4, + textColorStrong: basicColors.white, + textColorWeak: basicColors.gray2, + textColorEmphasis: basicColors.gray5, + textColorFaint: basicColors.dark5, + linkColor: new tinycolor(basicColors.white).darken(11).toString(), + linkColorDisabled: new tinycolor(basicColors.white).darken(11).toString(), + linkColorHover: basicColors.white, + linkColorExternal: basicColors.blue, + headingColor: new tinycolor(basicColors.white).darken(11).toString(), + } +} + +module.exports = darkTheme; diff --git a/packages/grafana-ui/src/themes/default.js b/packages/grafana-ui/src/themes/default.js new file mode 100644 index 00000000000..59ed050e360 --- /dev/null +++ b/packages/grafana-ui/src/themes/default.js @@ -0,0 +1,47 @@ + + +const theme = { + name: 'Grafana Default', + typography: { + fontFamily: { + sansSerif: "'Roboto', Helvetica, Arial, sans-serif;", + serif: "Georgia, 'Times New Roman', Times, serif;", + monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace;" + }, + size: { + base: '13px', + xs: '10px', + s: '12px', + m: '14px', + l: '18px', + }, + heading: { + h1: '2rem', + h2: '1.75rem', + h3: '1.5rem', + h4: '1.3rem', + h5: '1.2rem', + h6: '1rem', + }, + weight: { + light: 300, + normal: 400, + semibold: 500, + }, + lineHeight: { + xs: 1, + s: 1.1, + m: 4/3, + l: 1.5 + } + }, + brakpoints: { + xs: '0', + s: '544px', + m: '768px', + l: '992px', + xl: '1200px' + } +}; + +module.exports = theme; diff --git a/packages/grafana-ui/src/themes/light.js b/packages/grafana-ui/src/themes/light.js new file mode 100644 index 00000000000..84d1e656baa --- /dev/null +++ b/packages/grafana-ui/src/themes/light.js @@ -0,0 +1,65 @@ +// import { GrafanaThemeType } from "../theme"; + +const defaultTheme = require('./default'); +const tinycolor = require('tinycolor2'); + +const basicColors = { + black: '#000000', + white: '#ffffff', + dark1: '#13161d', + dark2: '#1e2028', + dark3: '#303133', + dark4: '#35373f', + dark5: '#41444b', + gray1: '#52545c', + gray2: '#767980', + gray3: '#acb6bf', + gray4: '#c7d0d9', + gray5: '#dde4ed', + gray6: '#e9edf2', + gray7: '#f7f8fa', + grayBlue: '#212327', // not used in light theme + blue: '#0083b3', + blueDark: '#005f81', + blueLight: '#00a8e6', + green: '#3aa655', + red: '#d44939', + yellow: '#ff851b', + pink: '#e671b8', + purple: '#9954bb', + variable: '#0083b3', + orange: '#ff7941', +}; + +const lightTheme/*: GrafanaThemeType*/ = { + ...defaultTheme, + name: 'Grafana Light', + colors: { + ...basicColors, + variable: basicColors.blue, + inputBlack: '#09090b', + queryRed: basicColors.red, + queryGreen: basicColors.green, + queryPurple: basicColors.purple, + queryKeyword: basicColors.blue, + queryOrange: basicColors.orange, + online: '#01a64f', + warn: '#f79520', + critical: '#ec2128', + bodyBg: basicColors.gray7, + pageBg: basicColors.gray7, + bodyColor: basicColors.gray1, + textColor: basicColors.gray1, + textColorStrong: basicColors.dark2, + textColorWeak: basicColors.gray2, + textColorEmphasis: basicColors.gray5, + textColorFaint: basicColors.dark4, + linkColor: basicColors.gray1, + linkColorDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(), + linkColorHover: new tinycolor(basicColors.gray1).darken(20).toString(), + linkColorExternal: basicColors.blueLight, + headingColor: basicColors.gray1, + } +} + +module.exports = lightTheme; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 7b0ed869bdc..61e2c8bfc76 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -3,73 +3,69 @@ $theme-name: dark; -// Grays // ------------------------- -$black: #000; +$black: getThemeVariable('colors.black', $theme-name); +$dark-1: getThemeVariable('colors.dark1', $theme-name); +$dark-2: getThemeVariable('colors.dark2', $theme-name); +$dark-3: getThemeVariable('colors.dark3', $theme-name); +$dark-4: getThemeVariable('colors.dark4', $theme-name); +$dark-5: getThemeVariable('colors.dark5', $theme-name); +$gray-1: getThemeVariable('colors.gray1', $theme-name); +$gray-2: getThemeVariable('colors.gray2', $theme-name); +$gray-3: getThemeVariable('colors.gray3', $theme-name); +$gray-4: getThemeVariable('colors.gray4', $theme-name); +$gray-5: getThemeVariable('colors.gray5', $theme-name); +$gray-6: getThemeVariable('colors.gray6', $theme-name); +$gray-7: getThemeVariable('colors.gray7', $theme-name); -// ------------------------- -$black: #000; -$dark-1: #141414; -$dark-2: #1f1f20; -$dark-3: #262628; -$dark-4: #333333; -$dark-5: #444444; -$gray-1: #555555; -$gray-2: #8e8e8e; -$gray-3: #b3b3b3; -$gray-4: #d8d9da; -$gray-5: #ececec; -$gray-6: #f4f5f8; -$gray-7: #fbfbfb; +$gray-blue: getThemeVariable('colors.grayBlue', $theme-name); +$input-black: getThemeVariable('colors.inputBlack', $theme-name); -$gray-blue: #212327; -$input-black: #09090b; - -$white: #fff; +$white: getThemeVariable('colors.white', $theme-name); // Accent colors // ------------------------- -$blue: #33b5e5; -$blue-dark: #005f81; -$green: #299c46; -$red: #d44a3a; -$yellow: #ecbb13; -$pink: #ff4444; -$purple: #9933cc; -$variable: #32d1df; -$orange: #eb7b18; +$blue: getThemeVariable('colors.blue', $theme-name); +$blue-dark: getThemeVariable('colors.blueDark', $theme-name); +$green: getThemeVariable('colors.green', $theme-name); +$red: getThemeVariable('colors.red', $theme-name); +$yellow: getThemeVariable('colors.yellow', $theme-name); +$pink: getThemeVariable('colors.pink', $theme-name); +$purple: getThemeVariable('colors.purple', $theme-name); +$variable: getThemeVariable('colors.variable', $theme-name); +$orange: getThemeVariable('colors.orange', $theme-name); $brand-primary: $orange; $brand-success: $green; $brand-warning: $brand-primary; $brand-danger: $red; -$query-red: #e24d42; -$query-green: #74e680; -$query-purple: #fe85fc; -$query-keyword: #66d9ef; -$query-orange: $orange; +$query-red: getThemeVariable('colors.queryRed', $theme-name); +$query-green: getThemeVariable('colors.queryGreen', $theme-name); +$query-purple: getThemeVariable('colors.queryPurple', $theme-name); +$query-keyword: getThemeVariable('colors.queryKeyword', $theme-name); +$query-orange: getThemeVariable('colors.queryOrange', $theme-name); // Status colors // ------------------------- -$online: #10a345; -$warn: #f79520; -$critical: #ed2e18; +$online: getThemeVariable('colors.online', $theme-name); +$warn: getThemeVariable('colors.warn', $theme-name); +$critical: getThemeVariable('colors.critical', $theme-name); // Scaffolding // ------------------------- -$body-bg: rgb(23, 24, 25); -$page-bg: rgb(22, 23, 25); +$body-bg: getThemeVariable('colors.bodyBg', $theme-name); +$page-bg: getThemeVariable('colors.pageBg', $theme-name); -$body-color: $gray-4; -$text-color: $gray-4; -$text-color-strong: $white; -$text-color-weak: $gray-2; -$text-color-faint: $dark-5; -$text-color-emphasis: $gray-5; +$body-color: getThemeVariable('colors.bodyColor', $theme-name); +$text-color: getThemeVariable('colors.textColor', $theme-name); +$text-color-strong: getThemeVariable('colors.textColorStrong', $theme-name); +$text-color-weak: getThemeVariable('colors.textColorWeak', $theme-name); +$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name); +$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name); -$text-shadow-strong: 1px 1px 4px $black; -$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45); +$text-shadow-strong: 1px 1px 4px getThemeVariable('colors.black', $theme-name); +$text-shadow-faint: 1px 1px 4px #2d2d2d; // gradients $brand-gradient: linear-gradient( @@ -84,10 +80,11 @@ $edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); // Links // ------------------------- -$link-color: darken($white, 11%); -$link-color-disabled: darken($link-color, 30%); -$link-hover-color: $white; -$external-link-color: $blue; +$link-color: getThemeVariable('colors.linkColor', $theme-name); +$link-color-disabled: getThemeVariable('colors.linkColorDisabled', $theme-name); +$link-hover-color: getThemeVariable('colors.linkColorHover', $theme-name); + +$external-link-color: getThemeVariable('colors.linkColorExternal', $theme-name); // Typography // ------------------------- @@ -135,7 +132,7 @@ $list-item-shadow: $card-shadow; $empty-list-cta-bg: $gray-blue; // Scrollbars -$scrollbarBackground: #404357; +$scrollbarBackground: #aeb5df; $scrollbarBackground2: #3a3a3a; $scrollbarBorder: black; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 10c074e1481..ad2b22e201a 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -12,83 +12,85 @@ $theme-name: light; $black: #000; // ------------------------- -$black: #000; -$dark-1: #13161d; -$dark-2: #1e2028; -$dark-3: #303133; -$dark-4: #35373f; -$dark-5: #41444b; -$gray-1: #52545c; -$gray-2: #767980; -$gray-3: #acb6bf; -$gray-4: #c7d0d9; -$gray-5: #dde4ed; -$gray-6: #e9edf2; -$gray-7: #f7f8fa; +$black: getThemeVariable('colors.black', $theme-name); +$dark-1: getThemeVariable('colors.dark1', $theme-name); +$dark-2: getThemeVariable('colors.dark2', $theme-name); +$dark-3: getThemeVariable('colors.dark3', $theme-name); +$dark-4: getThemeVariable('colors.dark4', $theme-name); +$dark-5: getThemeVariable('colors.dark5', $theme-name); +$gray-1: getThemeVariable('colors.gray1', $theme-name); +$gray-2: getThemeVariable('colors.gray2', $theme-name); +$gray-3: getThemeVariable('colors.gray3', $theme-name); +$gray-4: getThemeVariable('colors.gray4', $theme-name); +$gray-5: getThemeVariable('colors.gray5', $theme-name); +$gray-6: getThemeVariable('colors.gray6', $theme-name); +$gray-7: getThemeVariable('colors.gray7', $theme-name); -$white: #fff; +$white: getThemeVariable('colors.white', $theme-name); // Accent colors // ------------------------- -$blue: #0083b3; -$blue-dark: #005f81; -$blue-light: #00a8e6; -$green: #3aa655; -$red: #d44939; -$yellow: #ff851b; -$orange: #ff7941; -$pink: #e671b8; -$purple: #9954bb; -$variable: $blue; +$blue: getThemeVariable('colors.blue', $theme-name); +$blue-dark: getThemeVariable('colors.blueDark', $theme-name); +$blue-light: getThemeVariable('colors.blueLight', $theme-name); +$green: getThemeVariable('colors.green', $theme-name); +$red: getThemeVariable('colors.red', $theme-name); +$yellow: getThemeVariable('colors.yellow', $theme-name); +$orange: getThemeVariable('colors.orange', $theme-name); +$pink: getThemeVariable('colors.pink', $theme-name); +$purple: getThemeVariable('colors.purple', $theme-name); +$variable: getThemeVariable('colors.variable', $theme-name); $brand-primary: $orange; $brand-success: $green; $brand-warning: $orange; $brand-danger: $red; -$query-red: $red; -$query-green: $green; -$query-purple: $purple; -$query-orange: $orange; -$query-keyword: $blue; +$query-red: getThemeVariable('colors.queryRed', $theme-name); +$query-green: getThemeVariable('colors.queryGreen', $theme-name); +$query-purple: getThemeVariable('colors.queryPurple', $theme-name); +$query-keyword: getThemeVariable('colors.queryKeyword', $theme-name); +$query-orange: getThemeVariable('colors.queryOrange', $theme-name); // Status colors // ------------------------- -$online: #01a64f; -$warn: #f79520; -$critical: #ec2128; +$online: getThemeVariable('colors.online', $theme-name); +$warn: getThemeVariable('colors.warn', $theme-name); +$critical: getThemeVariable('colors.critical', $theme-name); // Scaffolding // ------------------------- -$body-bg: $gray-7; -$page-bg: $gray-7; -$body-color: $gray-1; -$text-color: $gray-1; -$text-color-strong: $dark-2; -$text-color-weak: $gray-2; -$text-color-faint: $gray-4; -$text-color-emphasis: $dark-5; +$body-bg: getThemeVariable('colors.bodyBg', $theme-name); +$page-bg: getThemeVariable('colors.pageBg', $theme-name); + +$body-color: getThemeVariable('colors.bodyColor', $theme-name); +$text-color: getThemeVariable('colors.textColor', $theme-name); +$text-color-strong: getThemeVariable('colors.textColorStrong', $theme-name); +$text-color-weak: getThemeVariable('colors.textColorWeak', $theme-name); +$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name); +$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name); $text-shadow-strong: none; $text-shadow-faint: none; $textShadow: none; // gradients -$brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); +$brand-gradient: linear-gradient(to right, hsl(50, 100%, 50%) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); $page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px); $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%); // Links // ------------------------- -$link-color: $gray-1; -$link-color-disabled: lighten($link-color, 30%); -$link-hover-color: darken($link-color, 20%); -$external-link-color: $blue-light; +$link-color: getThemeVariable('colors.linkColor', $theme-name); +$link-color-disabled: getThemeVariable('colors.linkColorDisabled', $theme-name); +$link-hover-color: getThemeVariable('colors.linkColorHover', $theme-name); + +$external-link-color: getThemeVariable('colors.linkColorExternal', $theme-name); // Typography // ------------------------- -$headings-color: $text-color; +$headings-color: getThemeVariable('colors.headingColor', $theme-name); $abbr-border-color: $gray-2 !default; $text-muted: $text-color-weak; diff --git a/public/sass/_variables.scss b/public/sass/_variables.scss index 4e9e69c4d2f..eab0e8c7f5a 100644 --- a/public/sass/_variables.scss +++ b/public/sass/_variables.scss @@ -47,45 +47,45 @@ $enable-flex: true; // Typography // ------------------------- -$font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif; -$font-family-serif: Georgia, 'Times New Roman', Times, serif; -$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace; +$font-family-sans-serif: getThemeVariable('typography.fontFamily.sansSerif'); +$font-family-serif: getThemeVariable('typography.fontFamily.serif'); +$font-family-monospace: getThemeVariable('typography.fontFamily.monospace'); $font-family-base: $font-family-sans-serif !default; -$font-size-root: 14px !default; -$font-size-base: 13px !default; +$font-size-root: getThemeVariable('typography.size.m') !default; +$font-size-base: getThemeVariable('typography.size.base') !default; -$font-size-lg: 18px !default; -$font-size-md: 14px !default; -$font-size-sm: 12px !default; -$font-size-xs: 10px !default; +$font-size-lg: getThemeVariable('typography.size.l') !default; +$font-size-md: getThemeVariable('typography.size.m') !default; +$font-size-sm: getThemeVariable('typography.size.s') !default; +$font-size-xs: getThemeVariable('typography.size.xs') !default; -$line-height-base: 1.5 !default; -$font-weight-semi-bold: 500; +$line-height-base: getThemeVariable('typography.lineHeight.l') !default; +$font-weight-semi-bold: getThemeVariable('typography.weight.semibold'); -$font-size-h1: 2rem !default; -$font-size-h2: 1.75rem !default; -$font-size-h3: 1.5rem !default; -$font-size-h4: 1.3rem !default; -$font-size-h5: 1.2rem !default; -$font-size-h6: 1rem !default; +$font-size-h1: getThemeVariable('typography.heading.h1') !default; +$font-size-h2: getThemeVariable('typography.heading.h2') !default; +$font-size-h3: getThemeVariable('typography.heading.h3') !default; +$font-size-h4: getThemeVariable('typography.heading.h4') !default; +$font-size-h5: getThemeVariable('typography.heading.h5') !default; +$font-size-h6: getThemeVariable('typography.heading.h6') !default; $display1-size: 6rem !default; $display2-size: 5.5rem !default; $display3-size: 4.5rem !default; $display4-size: 3.5rem !default; -$display1-weight: 400 !default; -$display2-weight: 400 !default; -$display3-weight: 400 !default; -$display4-weight: 400 !default; +$display1-weight: getThemeVariable('typography.weight.normal') !default; +$display2-weight: getThemeVariable('typography.weight.normal') !default; +$display3-weight: getThemeVariable('typography.weight.normal') !default; +$display4-weight: getThe1meVariable('typography.weight.normal') !default; $lead-font-size: 1.25rem !default; $lead-font-weight: 300 !default; $headings-margin-bottom: ($spacer / 2) !default; $headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; -$headings-font-weight: 400 !default; +$headings-font-weight: getThemeVariable('typography.weight.normal') !default; $headings-line-height: 1.1 !default; $hr-border-width: $border-width !default; diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js new file mode 100644 index 00000000000..c0b6bc4ed79 --- /dev/null +++ b/scripts/webpack/getThemeVariable.js @@ -0,0 +1,53 @@ +const sass = require('node-sass'); +const sassUtils = require('node-sass-utils')(sass); +const { getTheme } = require('../../packages/grafana-ui/src/theme'); +const { get } = require('lodash'); +const tinycolor = require('tinycolor2'); + +const units = ['rem', 'em', 'vh', 'vw', 'vmin', 'vmax', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch']; +const matchDimension = value => value.match(/[a-zA-Z]+|[0-9]+/g); + +const isHex = value => { + const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6})$/gi; + return hexRegex.test(value); +}; + +const isDimension = value => { + if( typeof value !== "string") { + return false; + } + + const [val, unit] = matchDimension(value); + return units.indexOf(unit) > -1 +}; + +/** + * @param {SassString} variablePath + * @param {"dark"|"light"} themeName + */ +function getThemeVariable(variablePath, themeName) { + const theme = getTheme(themeName.getValue()); + const variable = get(theme, variablePath.getValue()); + + if (!variable) { + throw new Error(`${variablePath} is not defined fo ${themeName}`); + } + + if (isHex(variable)) { + const rgb = new tinycolor(variable).toRgb(); + const color = sass.types.Color(rgb.r, rgb.g, rgb.b); + return color; + } + + if (isDimension(variable)) { + const [value, unit] = matchDimension(variable) + + const tmp = new sassUtils.SassDimension(parseInt(value,10), unit); + // debugger + return sassUtils.castToSass(tmp) + } + + return sassUtils.castToSass(variable); +} + +module.exports = getThemeVariable; diff --git a/scripts/webpack/sass.rule.js b/scripts/webpack/sass.rule.js index 75455fc4184..78f6b60d33f 100644 --- a/scripts/webpack/sass.rule.js +++ b/scripts/webpack/sass.rule.js @@ -1,6 +1,7 @@ 'use strict'; -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const getThemeVariable = require('./getThemeVariable'); module.exports = function(options) { return { @@ -23,7 +24,15 @@ module.exports = function(options) { config: { path: __dirname + '/postcss.config.js' }, }, }, - { loader: 'sass-loader', options: { sourceMap: options.sourceMap } }, + { + loader: 'sass-loader', + options: { + sourceMap: options.sourceMap, + functions: { + 'getThemeVariable($themeVar, $themeName: dark)': getThemeVariable, + }, + }, + }, ], }; }; diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index b37e4c08592..4519e292c6b 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -8,6 +8,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const IgnoreNotFoundExportPlugin = require("./IgnoreNotFoundExportPlugin.js"); +const getThemeVariable = require("./getThemeVariable"); module.exports = merge(common, { entry: { @@ -85,7 +86,14 @@ module.exports = merge(common, { config: { path: __dirname + '/postcss.config.js' }, }, }, - 'sass-loader', // compiles Sass to CSS + { + loader: 'sass-loader', + options: { + functions: { + "getThemeVariable($themeVar, $themeName: dark)": getThemeVariable + } + } + } ], }, { diff --git a/yarn.lock b/yarn.lock index 169abd40ee4..bc94a05d702 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11910,6 +11910,11 @@ node-releases@^1.0.0-alpha.11, node-releases@^1.1.3: dependencies: semver "^5.3.0" +node-sass-utils@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/node-sass-utils/-/node-sass-utils-1.1.2.tgz#d03639cfa4fc962398ba3648ab466f0db7cc2131" + integrity sha1-0DY5z6T8liOYujZIq0ZvDbfMITE= + node-sass@^4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" From 1bc007e29cde20e5a1920aa57dee974edb119ae3 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 5 Feb 2019 16:53:19 +0100 Subject: [PATCH 07/50] Implemented theme context and renamed/moved theme related types --- packages/grafana-ui/src/index.ts | 3 +- .../grafana-ui/src/themes/ThemeContext.tsx | 20 ++++++++++ packages/grafana-ui/src/themes/dark.js | 3 +- packages/grafana-ui/src/themes/index.d.ts | 4 ++ .../src/{theme.js => themes/index.js} | 6 +-- packages/grafana-ui/src/themes/light.js | 1 + packages/grafana-ui/src/types/index.ts | 11 +---- .../src/{theme.d.ts => types/theme.ts} | 14 +++++-- .../src/utils/storybook/withTheme.tsx | 40 +++++++++++++++++++ scripts/webpack/getThemeVariable.js | 15 +++---- 10 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 packages/grafana-ui/src/themes/ThemeContext.tsx create mode 100644 packages/grafana-ui/src/themes/index.d.ts rename packages/grafana-ui/src/{theme.js => themes/index.js} (70%) rename packages/grafana-ui/src/{theme.d.ts => types/theme.ts} (92%) create mode 100644 packages/grafana-ui/src/utils/storybook/withTheme.tsx diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index 4ddc7c8485a..216f2f13bad 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,4 +1,5 @@ export * from './components'; export * from './types'; export * from './utils'; -export * from './theme'; +export * from './themes'; +export * from './themes/ThemeContext'; diff --git a/packages/grafana-ui/src/themes/ThemeContext.tsx b/packages/grafana-ui/src/themes/ThemeContext.tsx new file mode 100644 index 00000000000..a61a71d8af6 --- /dev/null +++ b/packages/grafana-ui/src/themes/ThemeContext.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { GrafanaThemeType, Themeable } from '../types'; +import { getTheme } from './index'; + +type Omit = Pick>; +type Subtract = Omit; + +// Use Grafana Dark theme by default +export const ThemeContext = React.createContext(getTheme(GrafanaThemeType.Dark)); + +export const withTheme =

(Component: React.ComponentType

) => { + const WithTheme: React.FunctionComponent> = props => { + // @ts-ignore + return {theme => }; + }; + + WithTheme.displayName = `WithTheme(${Component.displayName})`; + + return WithTheme; +}; diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.js index c80a4593f53..3031018bd56 100644 --- a/packages/grafana-ui/src/themes/dark.js +++ b/packages/grafana-ui/src/themes/dark.js @@ -4,7 +4,7 @@ const defaultTheme = require('./default'); const tinycolor = require('tinycolor2'); const basicColors = { - black: '#00ff00', + black: '#000000', white: '#ffffff', dark1: '#141414', dark2: '#1f1f20', @@ -33,6 +33,7 @@ const basicColors = { const darkTheme = { ...defaultTheme, + type: 'dark', name: 'Grafana Dark', colors: { ...basicColors, diff --git a/packages/grafana-ui/src/themes/index.d.ts b/packages/grafana-ui/src/themes/index.d.ts new file mode 100644 index 00000000000..304c478b46e --- /dev/null +++ b/packages/grafana-ui/src/themes/index.d.ts @@ -0,0 +1,4 @@ +import { GrafanaTheme } from "../types"; + +export function getTheme(themeName?: string): GrafanaTheme +export function mockTheme(themeMock: Partial): () => void diff --git a/packages/grafana-ui/src/theme.js b/packages/grafana-ui/src/themes/index.js similarity index 70% rename from packages/grafana-ui/src/theme.js rename to packages/grafana-ui/src/themes/index.js index 3d0695e2490..c88cf137574 100644 --- a/packages/grafana-ui/src/theme.js +++ b/packages/grafana-ui/src/themes/index.js @@ -1,5 +1,5 @@ -const darkTheme = require('./themes/dark'); -const lightTheme = require('./themes/light'); +const darkTheme = require('./dark'); +const lightTheme = require('./light'); const getTheme = name => (name === 'light' ? lightTheme : darkTheme); @@ -11,5 +11,5 @@ const mockTheme = mock => { module.exports = { getTheme, - mockTheme, + mockTheme }; diff --git a/packages/grafana-ui/src/themes/light.js b/packages/grafana-ui/src/themes/light.js index 84d1e656baa..de5c79e8319 100644 --- a/packages/grafana-ui/src/themes/light.js +++ b/packages/grafana-ui/src/themes/light.js @@ -33,6 +33,7 @@ const basicColors = { const lightTheme/*: GrafanaThemeType*/ = { ...defaultTheme, + type: 'light', name: 'Grafana Light', colors: { ...basicColors, diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts index e23b5e63af8..81bdf741f30 100644 --- a/packages/grafana-ui/src/types/index.ts +++ b/packages/grafana-ui/src/types/index.ts @@ -1,14 +1,7 @@ + export * from './data'; export * from './time'; export * from './panel'; export * from './plugin'; export * from './datasource'; - -export enum GrafanaTheme { - Light = 'light', - Dark = 'dark', -} - -export interface Themeable { - theme?: GrafanaTheme; -} +export * from './theme'; diff --git a/packages/grafana-ui/src/theme.d.ts b/packages/grafana-ui/src/types/theme.ts similarity index 92% rename from packages/grafana-ui/src/theme.d.ts rename to packages/grafana-ui/src/types/theme.ts index 015bcd16136..0fc81fa24e1 100644 --- a/packages/grafana-ui/src/theme.d.ts +++ b/packages/grafana-ui/src/types/theme.ts @@ -1,4 +1,10 @@ -export interface GrafanaThemeType { +export enum GrafanaThemeType { + Light = 'light', + Dark = 'dark', +} + +export interface GrafanaTheme { + type: GrafanaThemeType; name: string; // TODO: not sure if should be a part of theme brakpoints: { @@ -112,5 +118,7 @@ export interface GrafanaThemeType { headingColor: string; }; } -export function getTheme(): GrafanaThemeType -export function mockTheme(themeMock: Partial): () => void + +export interface Themeable { + theme: GrafanaTheme; +} diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx new file mode 100644 index 00000000000..b1a9bca013a --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { RenderFunction } from '@storybook/react'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { select } from '@storybook/addon-knobs'; +import { getTheme } from '../../themes'; +import { GrafanaThemeType } from '../../types'; + +const ThemableStory: React.FunctionComponent<{}> = ({ children }) => { + const themeKnob = select( + 'Theme', + { + Default: GrafanaThemeType.Dark, + Light: GrafanaThemeType.Light, + Dark: GrafanaThemeType.Dark, + }, + GrafanaThemeType.Dark + ); + + return ( + + {children} + + + ); +}; + +export const renderComponentWithTheme = (component: React.ComponentType, props: any) => { + return ( + + {theme => { + return React.createElement(component, { + ...props, + theme, + }); + }} + + ); +}; + +export const withTheme = (story: RenderFunction) => {story()}; diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js index c0b6bc4ed79..0db0a9842a8 100644 --- a/scripts/webpack/getThemeVariable.js +++ b/scripts/webpack/getThemeVariable.js @@ -1,8 +1,8 @@ const sass = require('node-sass'); const sassUtils = require('node-sass-utils')(sass); -const { getTheme } = require('../../packages/grafana-ui/src/theme'); const { get } = require('lodash'); const tinycolor = require('tinycolor2'); +const { getTheme } = require('@grafana/ui/src/themes'); const units = ['rem', 'em', 'vh', 'vw', 'vmin', 'vmax', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch']; const matchDimension = value => value.match(/[a-zA-Z]+|[0-9]+/g); @@ -13,12 +13,11 @@ const isHex = value => { }; const isDimension = value => { - if( typeof value !== "string") { + if (typeof value !== 'string') { return false; } - const [val, unit] = matchDimension(value); - return units.indexOf(unit) > -1 + return units.indexOf(unit) > -1; }; /** @@ -40,11 +39,9 @@ function getThemeVariable(variablePath, themeName) { } if (isDimension(variable)) { - const [value, unit] = matchDimension(variable) - - const tmp = new sassUtils.SassDimension(parseInt(value,10), unit); - // debugger - return sassUtils.castToSass(tmp) + const [value, unit] = matchDimension(variable); + const dimension = new sassUtils.SassDimension(parseInt(value, 10), unit); + return sassUtils.castToSass(dimension); } return sassUtils.castToSass(variable); From 6b1390b972c8e6d5494ed6a3b400ddc89d8fbca9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 5 Feb 2019 17:04:48 +0100 Subject: [PATCH 08/50] Update types and themes usage in components --- .../components/ColorPicker/ColorPicker.tsx | 12 ++-- .../ColorPicker/ColorPickerPopover.test.tsx | 11 ++-- .../ColorPicker/ColorPickerPopover.tsx | 28 ++++------ .../ColorPicker/NamedColorsGroup.tsx | 9 +-- .../ColorPicker/NamedColorsPalette.test.tsx | 9 +-- .../ColorPicker/SeriesColorPickerPopover.tsx | 5 +- .../ColorPicker/SpectrumPalette.tsx | 4 +- .../ColorPicker/SpectrumPalettePointer.tsx | 4 +- .../src/components/Gauge/Gauge.test.tsx | 2 + .../grafana-ui/src/components/Gauge/Gauge.tsx | 23 ++++---- .../ThresholdsEditor/ThresholdsEditor.tsx | 55 +++++++++++-------- packages/grafana-ui/src/components/index.ts | 4 +- .../src/utils/namedColorsPalette.test.ts | 16 +++--- .../src/utils/namedColorsPalette.ts | 14 ++--- public/app/core/angular_wrappers.ts | 4 +- public/app/core/utils/ConfigProvider.tsx | 19 ++++--- public/app/core/utils/react2angular.ts | 4 +- public/app/plugins/panel/gauge/GaugePanel.tsx | 7 +-- .../plugins/panel/gauge/GaugePanelOptions.tsx | 28 ++++------ .../panel/graph/Legend/LegendSeriesItem.tsx | 31 ++++------- .../app/plugins/panel/graph/data_processor.ts | 4 +- public/app/plugins/panel/graph/graph.ts | 7 ++- public/app/plugins/panel/graph/module.ts | 4 +- .../panel/graph/time_region_manager.ts | 8 +-- .../app/plugins/panel/heatmap/color_legend.ts | 4 +- public/app/plugins/panel/heatmap/rendering.ts | 4 +- public/app/plugins/panel/singlestat/module.ts | 8 +-- public/app/plugins/panel/table/module.ts | 4 +- public/app/plugins/panel/table/renderer.ts | 4 +- public/app/routes/ReactContainer.tsx | 3 +- 30 files changed, 171 insertions(+), 168 deletions(-) diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index b6cf176a24b..67201727a34 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -2,11 +2,11 @@ import React, { Component, createRef } from 'react'; import PopperController from '../Tooltip/PopperController'; import Popper, { RenderPopperArrowFn } from '../Tooltip/Popper'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { Themeable, GrafanaTheme } from '../../types'; +import { GrafanaThemeType, Themeable } from '../../types'; import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; import propDeprecationWarning from '../../utils/propDeprecationWarning'; - +import { withTheme } from '../../themes/ThemeContext'; type ColorPickerChangeHandler = (color: string) => void; export interface ColorPickerProps extends Themeable { @@ -57,7 +57,7 @@ export const colorPickerFactory = (

); }; @@ -95,7 +95,7 @@ export const colorPickerFactory = (
@@ -110,5 +110,5 @@ export const colorPickerFactory = ( }; }; -export const ColorPicker = colorPickerFactory(ColorPickerPopover, 'ColorPicker'); -export const SeriesColorPicker = colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker'); +export const ColorPicker = withTheme(colorPickerFactory(ColorPickerPopover, 'ColorPicker')); +export const SeriesColorPicker = withTheme(colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker')); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx index 28d66e7af86..444f0e658c8 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx @@ -4,7 +4,8 @@ import { ColorPickerPopover } from './ColorPickerPopover'; import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette'; import { ColorSwatch } from './NamedColorsGroup'; import { flatten } from 'lodash'; -import { GrafanaTheme } from '../../types'; +import { GrafanaThemeType } from '../../types'; +import { getTheme } from '../../themes'; const allColors = flatten(Array.from(getNamedColorPalette().values())); @@ -14,7 +15,7 @@ describe('ColorPickerPopover', () => { describe('rendering', () => { it('should render provided color as selected if color provided by name', () => { - const wrapper = mount( {}} />); + const wrapper = mount( {}} theme={getTheme()}/>); const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false); @@ -24,7 +25,7 @@ describe('ColorPickerPopover', () => { }); it('should render provided color as selected if color provided by hex', () => { - const wrapper = mount( {}} />); + const wrapper = mount( {}} theme={getTheme()} />); const selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); const notSelectedSwatches = wrapper.find(ColorSwatch).filterWhere(node => node.prop('isSelected') === false); @@ -45,7 +46,7 @@ describe('ColorPickerPopover', () => { it('should pass hex color value to onChange prop by default', () => { wrapper = mount( - + ); const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name); @@ -61,7 +62,7 @@ describe('ColorPickerPopover', () => { enableNamedColors color={BasicGreen.variants.dark} onChange={onChangeSpy} - theme={GrafanaTheme.Light} + theme={getTheme(GrafanaThemeType.Light)} /> ); const basicBlueSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicBlue.name); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index d2937a1caba..b4c77a5e373 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker'; -import { GrafanaTheme } from '../../types'; import { PopperContentProps } from '../Tooltip/PopperController'; import SpectrumPalette from './SpectrumPalette'; +import { GrafanaThemeType } from '@grafana/ui'; export interface Props extends ColorPickerProps, PopperContentProps { customPickers?: T; @@ -43,7 +43,7 @@ export class ColorPickerPopover extends React if (enableNamedColors) { return changeHandler(color); } - changeHandler(getColorFromHexRgbOrName(color, theme)); + changeHandler(getColorFromHexRgbOrName(color, theme.type)); }; handleTabChange = (tab: PickerType | keyof T) => { @@ -58,7 +58,9 @@ export class ColorPickerPopover extends React case 'spectrum': return ; case 'palette': - return ; + return ( + + ); default: return this.renderCustomPicker(activePicker); } @@ -88,11 +90,7 @@ export class ColorPickerPopover extends React <> {Object.keys(customPickers).map(key => { return ( -
+
{customPickers[key].name}
); @@ -103,21 +101,14 @@ export class ColorPickerPopover extends React render() { const { theme } = this.props; - const colorPickerTheme = theme || GrafanaTheme.Dark; - + const colorPickerTheme = theme.type || GrafanaThemeType.Dark; return (
-
+
Colors
-
+
Custom
{this.renderCustomPickerTabs()} @@ -128,3 +119,4 @@ export class ColorPickerPopover extends React ); } } + diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx index 91c4f21642a..91407bc6cc6 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent } from 'react'; -import { Themeable, GrafanaTheme } from '../../types'; +import { Themeable, GrafanaThemeType } from '../../types'; import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette'; import { Color } from 'csstype'; import { find, upperFirst } from 'lodash'; @@ -28,7 +28,8 @@ export const ColorSwatch: FunctionComponent = ({ }) => { const isSmall = variant === ColorSwatchVariant.Small; const swatchSize = isSmall ? '16px' : '32px'; - const selectedSwatchBorder = theme === GrafanaTheme.Light ? '#ffffff' : '#1A1B1F'; + const selectedSwatchBorder = theme.type === GrafanaThemeType.Light ? '#ffffff' : '#1A1B1F'; + const swatchStyles = { width: swatchSize, height: swatchSize, @@ -76,7 +77,7 @@ const NamedColorsGroup: FunctionComponent = ({ key={primaryColor.name} isSelected={primaryColor.name === selectedColor} variant={ColorSwatchVariant.Large} - color={getColorForTheme(primaryColor, theme)} + color={getColorForTheme(primaryColor, theme.type)} label={upperFirst(primaryColor.hue)} onClick={() => onColorSelect(primaryColor)} theme={theme} @@ -95,7 +96,7 @@ const NamedColorsGroup: FunctionComponent = ({ onColorSelect(color)} theme={theme} /> diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx index 171d26f5c56..7a1ba95e81d 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx @@ -3,7 +3,8 @@ import { mount, ReactWrapper } from 'enzyme'; import { NamedColorsPalette } from './NamedColorsPalette'; import { ColorSwatch } from './NamedColorsGroup'; import { getColorDefinitionByName } from '../../utils'; -import { GrafanaTheme } from '../../types'; +import { getTheme } from '../../themes'; +import { GrafanaThemeType } from '../../types'; describe('NamedColorsPalette', () => { @@ -17,18 +18,18 @@ describe('NamedColorsPalette', () => { }); it('should render provided color variant specific for theme', () => { - wrapper = mount( {}} />); + wrapper = mount( {}} />); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark); wrapper.unmount(); - wrapper = mount( {}} />); + wrapper = mount( {}} />); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.light); }); it('should render dar variant of provided color when theme not provided', () => { - wrapper = mount( {}} />); + wrapper = mount( {}} theme={getTheme()}/>); selectedSwatch = wrapper.find(ColorSwatch).findWhere(node => node.key() === BasicGreen.name); expect(selectedSwatch.prop('color')).toBe(BasicGreen.variants.dark); }); diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 3fa7a1f4a45..4cb8c15c002 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -4,6 +4,7 @@ import { ColorPickerPopover } from './ColorPickerPopover'; import { ColorPickerProps } from './ColorPicker'; import { PopperContentProps } from '../Tooltip/PopperController'; import { Switch } from '../Switch/Switch'; +import { withTheme } from '../../themes/ThemeContext'; export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperContentProps { yaxis?: number; @@ -12,7 +13,6 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperC export const SeriesColorPickerPopover: FunctionComponent = props => { const { yaxis, onToggleAxis, color, ...colorPickerProps } = props; - return ( void; } -const renderPointer = (theme?: GrafanaTheme) => (props: SpectrumPalettePointerProps) => ( +const renderPointer = (theme: GrafanaTheme) => (props: SpectrumPalettePointerProps) => ( ); @@ -92,7 +92,7 @@ const SpectrumPalette: React.FunctionComponent = ({ color, }} theme={theme} /> - +
); }; diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx index d0b2cbc4bff..18327e96769 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { GrafanaTheme, Themeable } from '../../types'; +import { GrafanaThemeType, Themeable } from '../../types'; export interface SpectrumPalettePointerProps extends Themeable { direction?: string; @@ -17,7 +17,7 @@ const SpectrumPalettePointer: React.FunctionComponent ({ plot: jest.fn(), @@ -24,6 +25,7 @@ const setup = (propOverrides?: object) => { width: 300, value: 25, decimals: 0, + theme: getTheme() }; Object.assign(props, propOverrides); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 04d89bf3f57..a7435a56b3c 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -1,13 +1,14 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { ValueMapping, Threshold, BasicGaugeColor, GrafanaTheme } from '../../types'; +import { ValueMapping, Threshold, BasicGaugeColor, GrafanaThemeType } from '../../types'; import { getMappedValue } from '../../utils/valueMappings'; import { getColorFromHexRgbOrName, getValueFormat } from '../../utils'; +import { Themeable } from '../../index'; type TimeSeriesValue = string | number | null; -export interface Props { +export interface Props extends Themeable { decimals: number; height: number; valueMappings: ValueMapping[]; @@ -22,7 +23,6 @@ export interface Props { unit: string; width: number; value: number; - theme?: GrafanaTheme; } const FONT_SCALE = 1; @@ -41,7 +41,7 @@ export class Gauge extends PureComponent { thresholds: [], unit: 'none', stat: 'avg', - theme: GrafanaTheme.Dark, + theme: GrafanaThemeType.Dark, }; componentDidMount() { @@ -77,19 +77,19 @@ export class Gauge extends PureComponent { const { thresholds, theme } = this.props; if (thresholds.length === 1) { - return getColorFromHexRgbOrName(thresholds[0].color, theme); + return getColorFromHexRgbOrName(thresholds[0].color, theme.type); } const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0]; if (atThreshold) { - return getColorFromHexRgbOrName(atThreshold.color, theme); + return getColorFromHexRgbOrName(atThreshold.color, theme.type); } const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value); if (belowThreshold.length > 0) { const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0]; - return getColorFromHexRgbOrName(nearestThreshold.color, theme); + return getColorFromHexRgbOrName(nearestThreshold.color, theme.type); } return BasicGaugeColor.Red; @@ -104,13 +104,13 @@ export class Gauge extends PureComponent { return [ ...thresholdsSortedByIndex.map(threshold => { if (threshold.index === 0) { - return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme) }; + return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme.type) }; } const previousThreshold = thresholdsSortedByIndex[threshold.index - 1]; - return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme) }; + return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme.type) }; }), - { value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme) }, + { value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme.type) }, ]; } @@ -126,7 +126,8 @@ export class Gauge extends PureComponent { const formattedValue = this.formatValue(value) as string; const dimension = Math.min(width, height * 1.3); - const backgroundColor = theme === GrafanaTheme.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; + const backgroundColor = theme.type === GrafanaThemeType.Light ? 'rgb(230,230,230)' : theme.colors.dark3; + const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio; const thresholdMarkersWidth = gaugeWidth / 5; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index c15f66cca54..b2a2e07c58d 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -1,11 +1,11 @@ import React, { PureComponent } from 'react'; -import { Threshold, Themeable } from '../../types'; +import { Threshold } from '../../types'; import { ColorPicker } from '../ColorPicker/ColorPicker'; import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; import { colors } from '../../utils'; -import { getColorFromHexRgbOrName } from '@grafana/ui'; +import { getColorFromHexRgbOrName, ThemeContext } from '@grafana/ui'; -export interface Props extends Themeable { +export interface Props { thresholds: Threshold[]; onChange: (thresholds: Threshold[]) => void; } @@ -164,7 +164,10 @@ export class ThresholdsEditor extends PureComponent {
{threshold.color && (
- this.onChangeThresholdColor(threshold, color)} /> + this.onChangeThresholdColor(threshold, color)} + />
)}
@@ -188,27 +191,35 @@ export class ThresholdsEditor extends PureComponent { render() { const { thresholds } = this.state; - const { theme } = this.props; return ( - -
- {thresholds.map((threshold, index) => { - return ( -
-
this.onAddThreshold(threshold.index + 1)}> - -
-
-
{this.renderInput(threshold)}
+ + {theme => { + return ( + +
+ {thresholds.map((threshold, index) => { + return ( +
+
this.onAddThreshold(threshold.index + 1)} + > + +
+
+
{this.renderInput(threshold)}
+
+ ); + })}
- ); - })} -
-
+ + ); + }} +
); } } diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 5cd677761b0..dc435a8844d 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -14,8 +14,8 @@ export { FormLabel } from './FormLabel/FormLabel'; export { FormField } from './FormField/FormField'; export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder'; -export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; -export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover'; +export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; +export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; export { Graph } from './Graph/Graph'; export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index c6a1aaf0dd0..f875b9966f1 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -5,20 +5,20 @@ import { getColorFromHexRgbOrName, getColorDefinitionByName, } from './namedColorsPalette'; -import { GrafanaTheme } from '../types/index'; +import { GrafanaThemeType } from '../types/index'; describe('colors', () => { const SemiDarkBlue = getColorDefinitionByName('semi-dark-blue'); describe('getColorDefinition', () => { it('returns undefined for unknown hex', () => { - expect(getColorDefinition('#ff0000', GrafanaTheme.Light)).toBeUndefined(); - expect(getColorDefinition('#ff0000', GrafanaTheme.Dark)).toBeUndefined(); + expect(getColorDefinition('#ff0000', GrafanaThemeType.Light)).toBeUndefined(); + expect(getColorDefinition('#ff0000', GrafanaThemeType.Dark)).toBeUndefined(); }); it('returns definition for known hex', () => { - expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue); - expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue); + expect(getColorDefinition(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue); + expect(getColorDefinition(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue); }); }); @@ -28,8 +28,8 @@ describe('colors', () => { }); it('returns name for known hex', () => { - expect(getColorName(SemiDarkBlue.variants.light, GrafanaTheme.Light)).toEqual(SemiDarkBlue.name); - expect(getColorName(SemiDarkBlue.variants.dark, GrafanaTheme.Dark)).toEqual(SemiDarkBlue.name); + expect(getColorName(SemiDarkBlue.variants.light, GrafanaThemeType.Light)).toEqual(SemiDarkBlue.name); + expect(getColorName(SemiDarkBlue.variants.dark, GrafanaThemeType.Dark)).toEqual(SemiDarkBlue.name); }); }); @@ -53,7 +53,7 @@ describe('colors', () => { }); it("returns correct variant's hex for known color if theme specified", () => { - expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light); + expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaThemeType.Light)).toBe(SemiDarkBlue.variants.light); }); it('returns color if specified as hex or rgb/a', () => { diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts index 5312b27ad26..a99a93f4207 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts @@ -1,5 +1,5 @@ import { flatten } from 'lodash'; -import { GrafanaTheme } from '../types'; +import { GrafanaThemeType } from '../types'; type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple'; @@ -68,7 +68,7 @@ export const getColorDefinitionByName = (name: Color): ColorDefinition => { return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.name === name)[0]; }; -export const getColorDefinition = (hex: string, theme: GrafanaTheme): ColorDefinition | undefined => { +export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorDefinition | undefined => { return flatten(Array.from(getNamedColorPalette().values())).filter(definition => definition.variants[theme] === hex)[0]; }; @@ -77,7 +77,7 @@ const isHex = (color: string) => { return hexRegex.test(color); }; -export const getColorName = (color?: string, theme?: GrafanaTheme): Color | undefined => { +export const getColorName = (color?: string, theme?: GrafanaThemeType): Color | undefined => { if (!color) { return undefined; } @@ -86,7 +86,7 @@ export const getColorName = (color?: string, theme?: GrafanaTheme): Color | unde return undefined; } if (isHex(color)) { - const definition = getColorDefinition(color, theme || GrafanaTheme.Dark); + const definition = getColorDefinition(color, theme || GrafanaThemeType.Dark); return definition ? definition.name : undefined; } @@ -98,7 +98,7 @@ export const getColorByName = (colorName: string) => { return definition.length > 0 ? definition[0] : undefined; }; -export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => { +export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType): string => { if (color.indexOf('rgb') > -1 || isHex(color)) { return color; } @@ -112,14 +112,14 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): s return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark; }; -export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => { +export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaThemeType) => { return theme ? color.variants[theme] : color.variants.dark; }; const buildNamedColorsPalette = () => { const palette = new Map(); - const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true); + const BasicGreen = buildColorDefinition('green', 'green', ['#56A64B', '#73BF69'], true); const DarkGreen = buildColorDefinition('green', 'dark-green', ['#19730E', '#37872D']); const SemiDarkGreen = buildColorDefinition('green', 'semi-dark-green', ['#37872D', '#56A64B']); const LightGreen = buildColorDefinition('green', 'light-green', ['#73BF69', '#96D98D']); diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 4806275e87d..6db442e7470 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -9,7 +9,7 @@ import { TagFilter } from './components/TagFilter/TagFilter'; import { SideMenu } from './components/sidemenu/SideMenu'; import { MetricSelect } from './components/Select/MetricSelect'; import AppNotificationList from './components/AppNotifications/AppNotificationList'; -import { ColorPicker, SeriesColorPickerPopover } from '@grafana/ui'; +import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui'; export function registerAngularDirectives() { react2AngularDirective('passwordStrength', PasswordStrength, ['password']); @@ -27,7 +27,7 @@ export function registerAngularDirectives() { 'color', ['onChange', { watchDepth: 'reference', wrapApply: true }], ]); - react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [ + react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [ 'color', 'series', 'onColorChange', diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index 6883401ad27..56b6fc3d8b9 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -1,6 +1,6 @@ import React from 'react'; import config, { Settings } from 'app/core/config'; -import { GrafanaTheme } from '@grafana/ui'; +import { GrafanaThemeType, ThemeContext, getTheme } from '@grafana/ui'; export const ConfigContext = React.createContext(config); export const ConfigConsumer = ConfigContext.Consumer; @@ -13,16 +13,21 @@ export const provideConfig = (component: React.ComponentType) => { return ConfigProvider; }; -interface ThemeProviderProps { - children: (theme: GrafanaTheme) => JSX.Element; -} +export const getCurrentThemeName = () => + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark; +export const getCurrentTheme = () => getTheme(getCurrentThemeName()); -export const ThemeProvider = ({ children }: ThemeProviderProps) => { +export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return ( - {({ bootData }) => { - return children(bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark); + {config => { + const currentTheme = getCurrentThemeName(); + return {children}; }} ); }; + +export const provideTheme = (component: React.ComponentType) => { + return provideConfig((props: any) => {React.createElement(component, { ...props })}); +}; diff --git a/public/app/core/utils/react2angular.ts b/public/app/core/utils/react2angular.ts index 1057f68fcda..eb4bccab267 100644 --- a/public/app/core/utils/react2angular.ts +++ b/public/app/core/utils/react2angular.ts @@ -1,11 +1,11 @@ import coreModule from 'app/core/core_module'; -import { provideConfig } from 'app/core/utils/ConfigProvider'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; export function react2AngularDirective(name: string, component: any, options: any) { coreModule.directive(name, [ 'reactDirective', reactDirective => { - return reactDirective(provideConfig(component), options); + return reactDirective(provideTheme(component), options); }, ]); } diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index b6f37dde94f..5cb256ee1aa 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; // Services & Utils -import { processTimeSeries } from '@grafana/ui'; +import { processTimeSeries, ThemeContext } from '@grafana/ui'; // Components import { Gauge } from '@grafana/ui'; @@ -10,7 +10,6 @@ import { Gauge } from '@grafana/ui'; // Types import { GaugeOptions } from './types'; import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; interface Props extends PanelProps {} @@ -38,7 +37,7 @@ export class GaugePanel extends PureComponent { } return ( - + {theme => ( { theme={theme} /> )} - + ); } } diff --git a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx index 655c596ce84..84726ac88bf 100644 --- a/public/app/plugins/panel/gauge/GaugePanelOptions.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelOptions.tsx @@ -11,7 +11,6 @@ import { import ValueOptions from 'app/plugins/panel/gauge/ValueOptions'; import GaugeOptionsEditor from './GaugeOptionsEditor'; import { GaugeOptions } from './types'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; export const defaultProps = { options: { @@ -46,24 +45,17 @@ export default class GaugePanelOptions extends PureComponent - {(theme) => ( - <> - - - - - - - - )} - + return ( + <> + + + + + + + + ); } } diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx index d62613319b2..e3de5b067ba 100644 --- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx +++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { TimeSeries } from 'app/core/core'; import { SeriesColorPicker } from '@grafana/ui'; -import { ThemeProvider } from 'app/core/utils/ConfigProvider'; +// import { ThemeProvider } from 'app/core/utils/ConfigProvider'; export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total']; @@ -168,24 +168,17 @@ class LegendSeriesIcon extends PureComponent - {theme => { - return ( - - - - - - ); - }} - + + + + + ); } } diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index 4141d36e273..0d4445e1981 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { colors, GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { colors, GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; import config from 'app/core/config'; @@ -113,7 +113,7 @@ export class DataProcessor { const series = new TimeSeries({ datapoints: datapoints, alias: alias, - color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark), + color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark), unit: seriesData.unit, }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index aeb540551b8..3800e147d9d 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -25,7 +25,8 @@ import ReactDOM from 'react-dom'; import { Legend, GraphLegendProps } from './Legend/Legend'; import { GraphCtrl } from './module'; -import { GrafanaTheme, getValueFormat } from '@grafana/ui'; +import { GrafanaThemeType, getValueFormat } from '@grafana/ui'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; class GraphElement { ctrl: GraphCtrl; @@ -53,7 +54,7 @@ class GraphElement { this.thresholdManager = new ThresholdManager(this.ctrl); this.timeRegionManager = new TimeRegionManager( this.ctrl, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark ); this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => { return this.sortedSeries; @@ -109,7 +110,7 @@ class GraphElement { onToggleAxis: this.ctrl.onToggleAxis, }; - const legendReactElem = React.createElement(Legend, legendProps); + const legendReactElem = React.createElement(provideTheme(Legend), legendProps); ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel()); } diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 68d982eab13..cb1c0d98269 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -10,7 +10,7 @@ import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { DataProcessor } from './data_processor'; import { axesEditorComponent } from './axes_editor'; import config from 'app/core/config'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; class GraphCtrl extends MetricsPanelCtrl { static template = template; @@ -244,7 +244,7 @@ class GraphCtrl extends MetricsPanelCtrl { } onColorChange = (series, color) => { - series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark)); + series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)); this.panel.aliasColors[series.alias] = color; this.render(); }; diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 2917583ff36..ea39927bf57 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -1,7 +1,7 @@ import 'vendor/flot/jquery.flot'; import _ from 'lodash'; import moment from 'moment'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; type TimeRegionColorDefinition = { fill: string; @@ -43,7 +43,7 @@ export function getColorModes() { }); } -function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition { +function getColor(timeRegion, theme: GrafanaThemeType): TimeRegionColorDefinition { if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) { timeRegion.colorMode = 'red'; } @@ -58,7 +58,7 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition { const colorMode = colorModes[timeRegion.colorMode]; if (colorMode.themeDependent === true) { - return theme === GrafanaTheme.Light ? colorMode.lightColor : colorMode.darkColor; + return theme === GrafanaThemeType.Light ? colorMode.lightColor : colorMode.darkColor; } return { @@ -71,7 +71,7 @@ export class TimeRegionManager { plot: any; timeRegions: any; - constructor(private panelCtrl, private theme: GrafanaTheme = GrafanaTheme.Dark) {} + constructor(private panelCtrl, private theme: GrafanaThemeType = GrafanaThemeType.Dark) {} draw(plot) { this.timeRegions = this.panelCtrl.panel.timeRegions; diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 81329fe297b..dea250abf74 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -5,7 +5,7 @@ import { contextSrv } from 'app/core/core'; import { tickStep } from 'app/core/utils/ticks'; import { getColorScale, getOpacityScale } from './color_scale'; import coreModule from 'app/core/core_module'; -import { GrafanaTheme, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; const LEGEND_HEIGHT_PX = 6; const LEGEND_WIDTH_PX = 100; @@ -250,7 +250,7 @@ function drawSimpleOpacityLegend(elem, options) { .attr('stroke-width', 0) .attr( 'fill', - getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark) + getColorFromHexRgbOrName(options.cardColor, contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark) ) .style('opacity', d => legendOpacityScale(d)); } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 6489c9e9895..63604382432 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -7,7 +7,7 @@ import * as ticksUtils from 'app/core/utils/ticks'; import { HeatmapTooltip } from './heatmap_tooltip'; import { mergeZeroBuckets } from './heatmap_data_converter'; import { getColorScale, getOpacityScale } from './color_scale'; -import { GrafanaTheme, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui'; +import { GrafanaThemeType, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui'; const MIN_CARD_SIZE = 1, CARD_PADDING = 1, @@ -663,7 +663,7 @@ export class HeatmapRenderer { if (this.panel.color.mode === 'opacity') { return getColorFromHexRgbOrName( this.panel.color.cardColor, - contextSrv.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark + contextSrv.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark ); } else { return this.colorScale(d.count); diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 2768951d2ba..4ea81ff8630 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -8,7 +8,7 @@ import kbn from 'app/core/utils/kbn'; import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; -import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; +import { GrafanaThemeType, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; class SingleStatCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -590,7 +590,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { lineWidth: 1, fillColor: getColorFromHexRgbOrName( panel.sparkline.fillColor, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark ), }, }, @@ -610,7 +610,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { data: data.flotpairs, color: getColorFromHexRgbOrName( panel.sparkline.lineColor, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark ), }; @@ -630,7 +630,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { // Map panel colors to hex or rgb/a values data.colorMap = panel.colors.map(color => - getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark) + getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark) ); const body = panel.gauge.show ? '' : getBigValueHtml(); diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 82763e1839a..3d82dd4df68 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -6,7 +6,7 @@ import { transformDataToTable } from './transformers'; import { tablePanelEditor } from './editor'; import { columnOptionsTab } from './column_options'; import { TableRenderer } from './renderer'; -import { GrafanaTheme } from '@grafana/ui'; +import { GrafanaThemeType } from '@grafana/ui'; class TablePanelCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -131,7 +131,7 @@ class TablePanelCtrl extends MetricsPanelCtrl { this.dashboard.isTimezoneUtc(), this.$sanitize, this.templateSrv, - config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark, + config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark, ); return super.render(this.table); diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index 90479a67602..e9bf89f45fe 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import moment from 'moment'; import kbn from 'app/core/utils/kbn'; -import { GrafanaTheme, getValueFormat, getColorFromHexRgbOrName } from '@grafana/ui'; +import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType } from '@grafana/ui'; export class TableRenderer { formatters: any[]; @@ -13,7 +13,7 @@ export class TableRenderer { private isUtc, private sanitize, private templateSrv, - private theme?: GrafanaTheme + private theme?: GrafanaThemeType ) { this.initColumns(); } diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index 2cad3d828bf..985914eb067 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -5,6 +5,7 @@ import { Provider } from 'react-redux'; import coreModule from 'app/core/core_module'; import { store } from 'app/store/store'; import { ContextSrv } from 'app/core/services/context_srv'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; function WrapInProvider(store, Component, props) { return ( @@ -46,7 +47,7 @@ export function reactContainer( $scope: scope, }; - ReactDOM.render(WrapInProvider(store, component, props), elem[0]); + ReactDOM.render(WrapInProvider(store, provideTheme(component), props), elem[0]); scope.$on('$destroy', () => { ReactDOM.unmountComponentAtNode(elem[0]); From 7eb2558fc5d8d99588f41734c887fab8ac7c1f47 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 6 Feb 2019 15:06:27 +0100 Subject: [PATCH 09/50] Fix issue with graph legend color picker disapearing on color selection --- public/app/core/utils/ConfigProvider.tsx | 3 +-- public/app/plugins/panel/graph/graph.ts | 5 ++++- scripts/webpack/getThemeVariable.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index 56b6fc3d8b9..cb3ad88b191 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -21,8 +21,7 @@ export const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return ( {config => { - const currentTheme = getCurrentThemeName(); - return {children}; + return {children}; }} ); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 3800e147d9d..846d11ea475 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -28,6 +28,8 @@ import { GraphCtrl } from './module'; import { GrafanaThemeType, getValueFormat } from '@grafana/ui'; import { provideTheme } from 'app/core/utils/ConfigProvider'; +const LegendWithThemeProvider = provideTheme(Legend); + class GraphElement { ctrl: GraphCtrl; tooltip: any; @@ -44,6 +46,7 @@ class GraphElement { legendElem: HTMLElement; constructor(private scope, private elem, private timeSrv) { + this.ctrl = scope.ctrl; this.dashboard = this.ctrl.dashboard; this.panel = this.ctrl.panel; @@ -110,7 +113,7 @@ class GraphElement { onToggleAxis: this.ctrl.onToggleAxis, }; - const legendReactElem = React.createElement(provideTheme(Legend), legendProps); + const legendReactElem = React.createElement(LegendWithThemeProvider, legendProps); ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel()); } diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js index 0db0a9842a8..6726f95d47c 100644 --- a/scripts/webpack/getThemeVariable.js +++ b/scripts/webpack/getThemeVariable.js @@ -29,7 +29,7 @@ function getThemeVariable(variablePath, themeName) { const variable = get(theme, variablePath.getValue()); if (!variable) { - throw new Error(`${variablePath} is not defined fo ${themeName}`); + throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()} theme`); } if (isHex(variable)) { From 7762d72ae30537058bd89ff0747846dabdda9f0f Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 6 Feb 2019 17:03:42 +0100 Subject: [PATCH 10/50] Updated stories to use new theming --- packages/grafana-ui/.storybook/config.ts | 7 +++- .../grafana-ui/.storybook/webpack.config.js | 16 +++++++- .../ColorPicker/ColorPicker.story.tsx | 32 +++++++-------- .../ColorPicker/ColorPickerPopover.story.tsx | 41 +++++++------------ .../ColorPicker/NamedColorsPalette.story.tsx | 15 +++++-- .../ColorPicker/SpectrumPalette.story.tsx | 9 ++-- .../src/utils/storybook/withTheme.tsx | 3 +- 7 files changed, 64 insertions(+), 59 deletions(-) diff --git a/packages/grafana-ui/.storybook/config.ts b/packages/grafana-ui/.storybook/config.ts index 9e50c6b501a..434e717bbab 100644 --- a/packages/grafana-ui/.storybook/config.ts +++ b/packages/grafana-ui/.storybook/config.ts @@ -1,10 +1,15 @@ -import { configure } from '@storybook/react'; +import { configure, addDecorator } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs'; +import { withTheme } from '../src/utils/storybook/withTheme'; import '../../../public/sass/grafana.light.scss'; // automatically import all files ending in *.stories.tsx const req = require.context('../src/components', true, /.story.tsx$/); +addDecorator(withKnobs); +addDecorator(withTheme); + function loadStories() { req.keys().forEach(req); } diff --git a/packages/grafana-ui/.storybook/webpack.config.js b/packages/grafana-ui/.storybook/webpack.config.js index 44de73a1e18..4f27b71bb60 100644 --- a/packages/grafana-ui/.storybook/webpack.config.js +++ b/packages/grafana-ui/.storybook/webpack.config.js @@ -1,7 +1,7 @@ const path = require('path'); +const getThemeVariable = require('../../../scripts/webpack/getThemeVariable'); module.exports = (baseConfig, env, config) => { - config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ @@ -33,7 +33,15 @@ module.exports = (baseConfig, env, config) => { config: { path: __dirname + '../../../../scripts/webpack/postcss.config.js' }, }, }, - { loader: 'sass-loader', options: { sourceMap: false } }, + { + loader: 'sass-loader', + options: { + sourceMap: false, + functions: { + 'getThemeVariable($themeVar, $themeName: dark)': getThemeVariable, + }, + }, + }, ], }); @@ -52,5 +60,9 @@ module.exports = (baseConfig, env, config) => { }); config.resolve.extensions.push('.ts', '.tsx'); + + // Remove pure js loading rules as Storybook's Babel config is causing problems when mixing ES6 and CJS + // More about the problem we encounter: https://github.com/webpack/webpack/issues/4039 + config.module.rules = config.module.rules.filter(rule => rule.test.toString() !== /\.(mjs|jsx?)$/.toString()); return config; }; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index 19ae2fda978..1fb31e86d72 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -1,46 +1,43 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { withKnobs, boolean } from '@storybook/addon-knobs'; +import { boolean } from '@storybook/addon-knobs'; import { SeriesColorPicker, ColorPicker } from './ColorPicker'; import { action } from '@storybook/addon-actions'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UseState } from '../../utils/storybook/UseState'; -import { getThemeKnob } from '../../utils/storybook/themeKnob'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const getColorPickerKnobs = () => { return { - selectedTheme: getThemeKnob(), enableNamedColors: boolean('Enable named colors', false), }; }; const ColorPickerStories = storiesOf('UI/ColorPicker/Pickers', module); -ColorPickerStories.addDecorator(withCenteredStory).addDecorator(withKnobs); +ColorPickerStories.addDecorator(withCenteredStory); ColorPickerStories.add('default', () => { - const { selectedTheme, enableNamedColors } = getColorPickerKnobs(); + const { enableNamedColors } = getColorPickerKnobs(); + return ( {(selectedColor, updateSelectedColor) => { - return ( - { - action('Color changed')(color); - updateSelectedColor(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(ColorPicker, { + enableNamedColors, + color: selectedColor, + onChange: (color: any) => { + action('Color changed')(color); + updateSelectedColor(color); + }, + }); }} ); }); ColorPickerStories.add('Series color picker', () => { - const { selectedTheme, enableNamedColors } = getColorPickerKnobs(); + const { enableNamedColors } = getColorPickerKnobs(); return ( @@ -52,7 +49,6 @@ ColorPickerStories.add('Series color picker', () => { onToggleAxis={() => {}} color={selectedColor} onChange={color => updateSelectedColor(color)} - theme={selectedTheme || undefined} >
Open color picker
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx index dc51819a413..d749588ee31 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx @@ -1,40 +1,27 @@ -import React from 'react'; import { storiesOf } from '@storybook/react'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { withKnobs } from '@storybook/addon-knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { getThemeKnob } from '../../utils/storybook/themeKnob'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; - +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const ColorPickerPopoverStories = storiesOf('UI/ColorPicker/Popovers', module); -ColorPickerPopoverStories.addDecorator(withCenteredStory).addDecorator(withKnobs); +ColorPickerPopoverStories.addDecorator(withCenteredStory); ColorPickerPopoverStories.add('default', () => { - const selectedTheme = getThemeKnob(); - - return ( - { - console.log(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(ColorPickerPopover, { + color: '#BC67E6', + onChange: (color: any) => { + console.log(color); + }, + }); }); ColorPickerPopoverStories.add('SeriesColorPickerPopover', () => { - const selectedTheme = getThemeKnob(); - - return ( - { - console.log(color); - }} - theme={selectedTheme || undefined} - /> - ); + return renderComponentWithTheme(SeriesColorPickerPopover, { + color: '#BC67E6', + onChange: (color: any) => { + console.log(color); + }, + }); }); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx index af5de3b2a2d..f4901b28bfd 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorDefinitionByName } from '../../utils/namedColorsPalette'; -import { withKnobs, select } from '@storybook/addon-knobs'; +import { select } from '@storybook/addon-knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; import { UseState } from '../../utils/storybook/UseState'; const BasicGreen = getColorDefinitionByName('green'); @@ -12,7 +13,7 @@ const LightBlue = getColorDefinitionByName('light-blue'); const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module); -NamedColorsPaletteStories.addDecorator(withKnobs).addDecorator(withCenteredStory); +NamedColorsPaletteStories.addDecorator(withCenteredStory); NamedColorsPaletteStories.add('Named colors swatch - support for named colors', () => { const selectedColor = select( @@ -28,7 +29,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors', return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(NamedColorsPalette, { + color: selectedColor, + onChange: updateSelectedColor, + }); }} ); @@ -45,7 +49,10 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors', return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(NamedColorsPalette, { + color: getColorName(selectedColor), + onChange: updateSelectedColor, + }); }} ); diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx index b4fdaf69ed9..5fb6c569605 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx @@ -1,22 +1,19 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { withKnobs } from '@storybook/addon-knobs'; import SpectrumPalette from './SpectrumPalette'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UseState } from '../../utils/storybook/UseState'; -import { getThemeKnob } from '../../utils/storybook/themeKnob'; +import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; const SpectrumPaletteStories = storiesOf('UI/ColorPicker/Palettes/SpectrumPalette', module); -SpectrumPaletteStories.addDecorator(withCenteredStory).addDecorator(withKnobs); +SpectrumPaletteStories.addDecorator(withCenteredStory); SpectrumPaletteStories.add('default', () => { - const selectedTheme = getThemeKnob(); - return ( {(selectedColor, updateSelectedColor) => { - return ; + return renderComponentWithTheme(SpectrumPalette, { color: selectedColor, onChange: updateSelectedColor }); }} ); diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx index b1a9bca013a..5417af1de05 100644 --- a/packages/grafana-ui/src/utils/storybook/withTheme.tsx +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -9,7 +9,6 @@ const ThemableStory: React.FunctionComponent<{}> = ({ children }) => { const themeKnob = select( 'Theme', { - Default: GrafanaThemeType.Dark, Light: GrafanaThemeType.Light, Dark: GrafanaThemeType.Dark, }, @@ -24,6 +23,8 @@ const ThemableStory: React.FunctionComponent<{}> = ({ children }) => { ); }; +// Temporary solution. When we update to Storybook V5 we will be able to pass data from decorator to story +// https://github.com/storybooks/storybook/issues/340#issuecomment-456013702 export const renderComponentWithTheme = (component: React.ComponentType, props: any) => { return ( From 1e4c6b4b527df387946c34d13d3a8b7e58aa20df Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 6 Feb 2019 17:05:22 +0100 Subject: [PATCH 11/50] Added test for SASS variable retrieval function from JS definition --- jest.config.js | 2 ++ packages/grafana-ui/src/themes/index.js | 11 ++++--- scripts/webpack/getThemeVariable.js | 4 +-- scripts/webpack/getThemeVariable.test.js | 40 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 scripts/webpack/getThemeVariable.test.js diff --git a/jest.config.js b/jest.config.js index c5c6bcb9f5f..248435c43f2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,11 +6,13 @@ module.exports = { }, "moduleDirectories": ["node_modules", "public"], "roots": [ + "/scripts", "/public/app", "/public/test", "/packages" ], "testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$", + "testPathIgnorePatterns": ["webpack.test.js"], "moduleFileExtensions": [ "ts", "tsx", diff --git a/packages/grafana-ui/src/themes/index.js b/packages/grafana-ui/src/themes/index.js index c88cf137574..91ba25349fd 100644 --- a/packages/grafana-ui/src/themes/index.js +++ b/packages/grafana-ui/src/themes/index.js @@ -1,15 +1,16 @@ const darkTheme = require('./dark'); const lightTheme = require('./light'); -const getTheme = name => (name === 'light' ? lightTheme : darkTheme); +let mockedTheme; + +let getTheme = name => mockedTheme || (name === 'light' ? lightTheme : darkTheme); const mockTheme = mock => { - const originalGetTheme = getTheme; - getTheme = () => mock; - return () => (getTheme = originalGetTheme); + mockedTheme = mock; + return () => (mockedTheme = null); }; module.exports = { getTheme, - mockTheme + mockTheme, }; diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js index 6726f95d47c..3bd9b2a53d0 100644 --- a/scripts/webpack/getThemeVariable.js +++ b/scripts/webpack/getThemeVariable.js @@ -29,12 +29,12 @@ function getThemeVariable(variablePath, themeName) { const variable = get(theme, variablePath.getValue()); if (!variable) { - throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()} theme`); + throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()}`); } if (isHex(variable)) { const rgb = new tinycolor(variable).toRgb(); - const color = sass.types.Color(rgb.r, rgb.g, rgb.b); + const color = new sass.types.Color(rgb.r, rgb.g, rgb.b); return color; } diff --git a/scripts/webpack/getThemeVariable.test.js b/scripts/webpack/getThemeVariable.test.js new file mode 100644 index 00000000000..78083330890 --- /dev/null +++ b/scripts/webpack/getThemeVariable.test.js @@ -0,0 +1,40 @@ +const sass = require('node-sass'); +const getThemeVariable = require('./getThemeVariable'); +const { mockTheme } = require('@grafana/ui'); + +const themeMock = { + color: { + background: '#ff0000', + }, + spacing: { + padding: '2em', + }, + typography: { + fontFamily: 'Arial, sans-serif', + }, +}; + +describe('Variables retrieval', () => { + const restoreTheme = mockTheme(themeMock); + + afterAll(() => { + restoreTheme(); + }); + + it('returns sass Color for color values', () => { + const result = getThemeVariable({ getValue: () => 'color.background' }, { getValue: () => {} }); + expect(result).toBeInstanceOf(sass.types.Color); + }); + it('returns sass Number for dimension values', () => { + const result = getThemeVariable({ getValue: () => 'spacing.padding' }, { getValue: () => {} }); + expect(result).toBeInstanceOf(sass.types.Number); + }); + it('returns sass String for string values', () => { + const result = getThemeVariable({ getValue: () => 'typography.fontFamily' }, { getValue: () => {} }); + expect(result).toBeInstanceOf(sass.types.String); + }); + + it('throws for unknown theme paths', () => { + expect(() => getThemeVariable({ getValue: () => 'what.ever' }, { getValue: () => {} })).toThrow(); + }); +}); From dc6b27d123bd069a299a5ce8007fc14e98798fff Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 6 Feb 2019 17:05:43 +0100 Subject: [PATCH 12/50] Minor cleanup --- packages/grafana-ui/src/themes/dark.js | 2 -- .../grafana-ui/src/utils/storybook/themeKnob.ts | 14 -------------- 2 files changed, 16 deletions(-) delete mode 100644 packages/grafana-ui/src/utils/storybook/themeKnob.ts diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.js index 3031018bd56..553eb537093 100644 --- a/packages/grafana-ui/src/themes/dark.js +++ b/packages/grafana-ui/src/themes/dark.js @@ -1,5 +1,3 @@ - - const defaultTheme = require('./default'); const tinycolor = require('tinycolor2'); diff --git a/packages/grafana-ui/src/utils/storybook/themeKnob.ts b/packages/grafana-ui/src/utils/storybook/themeKnob.ts deleted file mode 100644 index a3733462bea..00000000000 --- a/packages/grafana-ui/src/utils/storybook/themeKnob.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { select } from '@storybook/addon-knobs'; -import { GrafanaTheme } from '../../types'; - -export const getThemeKnob = (defaultTheme: GrafanaTheme = GrafanaTheme.Dark) => { - return select( - 'Theme', - { - Default: defaultTheme, - Light: GrafanaTheme.Light, - Dark: GrafanaTheme.Dark, - }, - defaultTheme - ); -}; From 5ba3b0aa2ce8aa79b76a8f3f9de6197be5a8d198 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 7 Feb 2019 14:10:20 +0100 Subject: [PATCH 13/50] Selecting theme variable variant helper function --- .../ColorPicker/NamedColorsGroup.tsx | 12 ++++- .../ColorPicker/SpectrumPalettePointer.tsx | 17 ++++--- packages/grafana-ui/src/themes/index.d.ts | 2 +- packages/grafana-ui/src/themes/index.js | 2 +- .../src/themes/selectThemeVariant.test.ts | 51 +++++++++++++++++++ .../src/themes/selectThemeVariant.ts | 9 ++++ scripts/webpack/getThemeVariable.test.js | 2 +- 7 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 packages/grafana-ui/src/themes/selectThemeVariant.test.ts create mode 100644 packages/grafana-ui/src/themes/selectThemeVariant.ts diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx index 91407bc6cc6..2b5f7bb9b57 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx @@ -1,8 +1,9 @@ import React, { FunctionComponent } from 'react'; -import { Themeable, GrafanaThemeType } from '../../types'; +import { Themeable } from '../../types'; import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette'; import { Color } from 'csstype'; import { find, upperFirst } from 'lodash'; +import { selectThemeVariant } from '../../themes/selectThemeVariant'; type ColorChangeHandler = (color: ColorDefinition) => void; @@ -28,7 +29,14 @@ export const ColorSwatch: FunctionComponent = ({ }) => { const isSmall = variant === ColorSwatchVariant.Small; const swatchSize = isSmall ? '16px' : '32px'; - const selectedSwatchBorder = theme.type === GrafanaThemeType.Light ? '#ffffff' : '#1A1B1F'; + + const selectedSwatchBorder = selectThemeVariant( + { + light: theme.colors.white, + dark: theme.colors.black, + }, + theme.type + ); const swatchStyles = { width: swatchSize, diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx index 18327e96769..7e3b2cf06a3 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalettePointer.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { GrafanaThemeType, Themeable } from '../../types'; +import { Themeable } from '../../types'; +import { selectThemeVariant } from '../../themes/selectThemeVariant'; export interface SpectrumPalettePointerProps extends Themeable { direction?: string; } -const SpectrumPalettePointer: React.FunctionComponent = ({ - theme, - direction, -}) => { +const SpectrumPalettePointer: React.FunctionComponent = ({ theme, direction }) => { const styles = { picker: { width: '16px', @@ -17,7 +15,14 @@ const SpectrumPalettePointer: React.FunctionComponent): () => void +export function mockTheme(themeMock: (name: string) => object): () => void diff --git a/packages/grafana-ui/src/themes/index.js b/packages/grafana-ui/src/themes/index.js index 91ba25349fd..21dd19cf119 100644 --- a/packages/grafana-ui/src/themes/index.js +++ b/packages/grafana-ui/src/themes/index.js @@ -3,7 +3,7 @@ const lightTheme = require('./light'); let mockedTheme; -let getTheme = name => mockedTheme || (name === 'light' ? lightTheme : darkTheme); +let getTheme = name => (mockedTheme && mockedTheme(name)) || (name === 'light' ? lightTheme : darkTheme); const mockTheme = mock => { mockedTheme = mock; diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts new file mode 100644 index 00000000000..86e35f515c2 --- /dev/null +++ b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts @@ -0,0 +1,51 @@ +import { GrafanaThemeType } from '../types/theme'; +import { selectThemeVariant } from './selectThemeVariant'; +import { mockTheme } from './index'; + +const lightThemeMock = { + color: { + red: '#ff0000', + green: '#00ff00', + }, +}; + +const darkThemeMock = { + color: { + red: '#ff0000', + green: '#00ff00', + }, +}; + +describe('Theme variable variant selector', () => { + const restoreTheme = mockTheme(name => (name === GrafanaThemeType.Light ? lightThemeMock : darkThemeMock)); + + afterAll(() => { + restoreTheme(); + }); + it('return correct variable value for given theme', () => { + const theme = lightThemeMock; + + const selectedValue = selectThemeVariant( + { + dark: theme.color.red, + light: theme.color.green, + }, + GrafanaThemeType.Light + ); + + expect(selectedValue).toBe(lightThemeMock.color.green); + }); + + it('return dark theme variant if no theme given', () => { + const theme = lightThemeMock; + + const selectedValue = selectThemeVariant( + { + dark: theme.color.red, + light: theme.color.green, + } + ); + + expect(selectedValue).toBe(lightThemeMock.color.red); + }); +}); diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.ts b/packages/grafana-ui/src/themes/selectThemeVariant.ts new file mode 100644 index 00000000000..e7e8e780222 --- /dev/null +++ b/packages/grafana-ui/src/themes/selectThemeVariant.ts @@ -0,0 +1,9 @@ +import { GrafanaThemeType } from '../types/theme'; + +type VariantDescriptor = { + [key in GrafanaThemeType]: string | number; +}; + +export const selectThemeVariant = (variants: VariantDescriptor, currentTheme?: GrafanaThemeType) => { + return variants[currentTheme || GrafanaThemeType.Dark]; +}; diff --git a/scripts/webpack/getThemeVariable.test.js b/scripts/webpack/getThemeVariable.test.js index 78083330890..57a6fb5236c 100644 --- a/scripts/webpack/getThemeVariable.test.js +++ b/scripts/webpack/getThemeVariable.test.js @@ -15,7 +15,7 @@ const themeMock = { }; describe('Variables retrieval', () => { - const restoreTheme = mockTheme(themeMock); + const restoreTheme = mockTheme(() => themeMock); afterAll(() => { restoreTheme(); From e7917ce4e047729cd7c92c9030bf70c3c7c235e5 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 7 Feb 2019 14:10:56 +0100 Subject: [PATCH 14/50] Removed unnecessary code from ColorPicker and extended theme type --- .../components/ColorPicker/ColorPicker.tsx | 19 +++---------------- packages/grafana-ui/src/themes/dark.js | 11 ++++++++--- packages/grafana-ui/src/themes/default.js | 15 +++++++++++++++ packages/grafana-ui/src/themes/light.js | 5 +++++ packages/grafana-ui/src/types/theme.ts | 7 ++++++- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 67201727a34..a48ecc44c45 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,8 +1,8 @@ import React, { Component, createRef } from 'react'; import PopperController from '../Tooltip/PopperController'; -import Popper, { RenderPopperArrowFn } from '../Tooltip/Popper'; +import Popper from '../Tooltip/Popper'; import { ColorPickerPopover } from './ColorPickerPopover'; -import { GrafanaThemeType, Themeable } from '../../types'; +import { Themeable } from '../../types'; import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; import propDeprecationWarning from '../../utils/propDeprecationWarning'; @@ -18,7 +18,6 @@ export interface ColorPickerProps extends Themeable { */ onColorChange?: ColorPickerChangeHandler; enableNamedColors?: boolean; - withArrow?: boolean; children?: JSX.Element; } @@ -32,7 +31,6 @@ export const warnAboutColorPickerPropsDeprecation = (componentName: string, prop export const colorPickerFactory = ( popover: React.ComponentType, displayName = 'ColorPicker', - renderPopoverArrowFunction?: RenderPopperArrowFn ) => { return class ColorPicker extends Component { static displayName = displayName; @@ -50,17 +48,7 @@ export const colorPickerFactory = ( ...this.props, onChange: this.handleColorChange, }); - const { theme, withArrow, children } = this.props; - - const renderArrow: RenderPopperArrowFn = ({ arrowProps, placement }) => { - return ( -
- ); - }; + const { theme, children } = this.props; return ( @@ -72,7 +60,6 @@ export const colorPickerFactory = ( {...popperProps} referenceElement={this.pickerTriggerRef.current} wrapperClassName="ColorPicker" - renderArrow={withArrow && (renderPopoverArrowFunction || renderArrow)} onMouseLeave={hidePopper} onMouseEnter={showPopper} /> diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.js index 553eb537093..d5e7e5aacba 100644 --- a/packages/grafana-ui/src/themes/dark.js +++ b/packages/grafana-ui/src/themes/dark.js @@ -33,7 +33,7 @@ const darkTheme = { ...defaultTheme, type: 'dark', name: 'Grafana Dark', - colors: { + colors: { ...basicColors, inputBlack: '#09090b', queryRed: '#e24d42', @@ -57,7 +57,12 @@ const darkTheme = { linkColorHover: basicColors.white, linkColorExternal: basicColors.blue, headingColor: new tinycolor(basicColors.white).darken(11).toString(), - } -} + }, + background: { + dropdown: basicColors.dark3, + scrollbar: '#aeb5df', + scrollbar2: '#3a3a3a', + }, +}; module.exports = darkTheme; diff --git a/packages/grafana-ui/src/themes/default.js b/packages/grafana-ui/src/themes/default.js index 59ed050e360..d95c6ad5689 100644 --- a/packages/grafana-ui/src/themes/default.js +++ b/packages/grafana-ui/src/themes/default.js @@ -41,6 +41,21 @@ const theme = { m: '768px', l: '992px', xl: '1200px' + }, + spacing: { + xs: '0', + s: '0.2rem', + m: '1rem', + l: '1.5rem', + xl: '3rem', + gutter: '30px', + }, + border: { + radius: { + xs: '2px', + s: '3px', + m: '5px', + } } }; diff --git a/packages/grafana-ui/src/themes/light.js b/packages/grafana-ui/src/themes/light.js index de5c79e8319..8da6190caba 100644 --- a/packages/grafana-ui/src/themes/light.js +++ b/packages/grafana-ui/src/themes/light.js @@ -60,6 +60,11 @@ const lightTheme/*: GrafanaThemeType*/ = { linkColorHover: new tinycolor(basicColors.gray1).darken(20).toString(), linkColorExternal: basicColors.blueLight, headingColor: basicColors.gray1, + }, + background: { + dropdown: basicColors.white, + scrollbar: basicColors.gray5, + scrollbar2: basicColors.gray5, } } diff --git a/packages/grafana-ui/src/types/theme.ts b/packages/grafana-ui/src/types/theme.ts index 0fc81fa24e1..8a79658b423 100644 --- a/packages/grafana-ui/src/types/theme.ts +++ b/packages/grafana-ui/src/types/theme.ts @@ -62,6 +62,11 @@ export interface GrafanaTheme { m: string; }; }; + background: { + dropdown: string; + scrollbar: string; + scrollbar2: string; + }; colors: { black: string; white: string; @@ -102,7 +107,7 @@ export interface GrafanaTheme { warn: string; critical: string; - // TODO: should this be a part of theme? + // TODO: move to background section bodyBg: string; pageBg: string; bodyColor: string; From 6e7941d39603aabc12df50e7f74dd27f1f95e393 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 7 Feb 2019 13:13:01 +0100 Subject: [PATCH 15/50] moves usage stats sender to new package --- pkg/infra/usagestats/service.go | 54 ++++++ pkg/infra/usagestats/usage_stats.go | 168 ++++++++++++++++ .../usagestats/usage_stats_test.go} | 21 +- pkg/metrics/metrics.go | 181 +----------------- pkg/metrics/service.go | 22 +-- pkg/metrics/settings.go | 4 - 6 files changed, 247 insertions(+), 203 deletions(-) create mode 100644 pkg/infra/usagestats/service.go create mode 100644 pkg/infra/usagestats/usage_stats.go rename pkg/{metrics/metrics_test.go => infra/usagestats/usage_stats_test.go} (94%) diff --git a/pkg/infra/usagestats/service.go b/pkg/infra/usagestats/service.go new file mode 100644 index 00000000000..f853c03302d --- /dev/null +++ b/pkg/infra/usagestats/service.go @@ -0,0 +1,54 @@ +package usagestats + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/social" + + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +var metricsLogger log.Logger = log.New("metrics") + +func init() { + registry.RegisterService(&UsageStatsService{}) +} + +type UsageStatsService struct { + Cfg *setting.Cfg `inject:""` + TokenService *auth.UserAuthTokenService `inject:""` + Bus bus.Bus `inject:""` + + oauthProviders map[string]bool +} + +func (uss *UsageStatsService) Init() error { + + uss.oauthProviders = social.GetOAuthProviders(uss.Cfg) + return nil +} + +func (uss *UsageStatsService) Run(ctx context.Context) error { + uss.updateTotalStats() + + onceEveryDayTick := time.NewTicker(time.Hour * 24) + everyMinuteTicker := time.NewTicker(time.Minute) + defer onceEveryDayTick.Stop() + defer everyMinuteTicker.Stop() + + for { + select { + case <-onceEveryDayTick.C: + uss.sendUsageStats(uss.oauthProviders) + case <-everyMinuteTicker.C: + uss.updateTotalStats() + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go new file mode 100644 index 00000000000..b0dc52ccd8b --- /dev/null +++ b/pkg/infra/usagestats/usage_stats.go @@ -0,0 +1,168 @@ +package usagestats + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "runtime" + "strings" + "time" + + "github.com/grafana/grafana/pkg/metrics" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/setting" +) + +var usageStatsURL = "https://stats.grafana.org/grafana-usage-report" + +func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { + if !setting.ReportingEnabled { + return + } + + metricsLogger.Debug(fmt.Sprintf("Sending anonymous usage stats to %s", usageStatsURL)) + + version := strings.Replace(setting.BuildVersion, ".", "_", -1) + + metrics := map[string]interface{}{} + report := map[string]interface{}{ + "version": version, + "metrics": metrics, + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "edition": getEdition(), + "packaging": setting.Packaging, + } + + statsQuery := models.GetSystemStatsQuery{} + if err := uss.Bus.Dispatch(&statsQuery); err != nil { + metricsLogger.Error("Failed to get system stats", "error", err) + return + } + + metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards + metrics["stats.users.count"] = statsQuery.Result.Users + metrics["stats.orgs.count"] = statsQuery.Result.Orgs + metrics["stats.playlist.count"] = statsQuery.Result.Playlists + metrics["stats.plugins.apps.count"] = len(plugins.Apps) + metrics["stats.plugins.panels.count"] = len(plugins.Panels) + metrics["stats.plugins.datasources.count"] = len(plugins.DataSources) + metrics["stats.alerts.count"] = statsQuery.Result.Alerts + metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers + metrics["stats.datasources.count"] = statsQuery.Result.Datasources + metrics["stats.stars.count"] = statsQuery.Result.Stars + metrics["stats.folders.count"] = statsQuery.Result.Folders + metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions + metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions + metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards + metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots + metrics["stats.teams.count"] = statsQuery.Result.Teams + + dsStats := models.GetDataSourceStatsQuery{} + if err := uss.Bus.Dispatch(&dsStats); err != nil { + metricsLogger.Error("Failed to get datasource stats", "error", err) + return + } + + // send counters for each data source + // but ignore any custom data sources + // as sending that name could be sensitive information + dsOtherCount := 0 + for _, dsStat := range dsStats.Result { + if models.IsKnownDataSourcePlugin(dsStat.Type) { + metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count + } else { + dsOtherCount += dsStat.Count + } + } + metrics["stats.ds.other.count"] = dsOtherCount + + metrics["stats.packaging."+setting.Packaging+".count"] = 1 + + dsAccessStats := models.GetDataSourceAccessStatsQuery{} + if err := uss.Bus.Dispatch(&dsAccessStats); err != nil { + metricsLogger.Error("Failed to get datasource access stats", "error", err) + return + } + + // send access counters for each data source + // but ignore any custom data sources + // as sending that name could be sensitive information + dsAccessOtherCount := make(map[string]int64) + for _, dsAccessStat := range dsAccessStats.Result { + if dsAccessStat.Access == "" { + continue + } + + access := strings.ToLower(dsAccessStat.Access) + + if models.IsKnownDataSourcePlugin(dsAccessStat.Type) { + metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count + } else { + old := dsAccessOtherCount[access] + dsAccessOtherCount[access] = old + dsAccessStat.Count + } + } + + for access, count := range dsAccessOtherCount { + metrics["stats.ds_access.other."+access+".count"] = count + } + + anStats := models.GetAlertNotifierUsageStatsQuery{} + if err := uss.Bus.Dispatch(&anStats); err != nil { + metricsLogger.Error("Failed to get alert notification stats", "error", err) + return + } + + for _, stats := range anStats.Result { + metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count + } + + authTypes := map[string]bool{} + authTypes["anonymous"] = setting.AnonymousEnabled + authTypes["basic_auth"] = setting.BasicAuthEnabled + authTypes["ldap"] = setting.LdapEnabled + authTypes["auth_proxy"] = setting.AuthProxyEnabled + + for provider, enabled := range oauthProviders { + authTypes["oauth_"+provider] = enabled + } + + for authType, enabled := range authTypes { + enabledValue := 0 + if enabled { + enabledValue = 1 + } + metrics["stats.auth_enabled."+authType+".count"] = enabledValue + } + + out, _ := json.MarshalIndent(report, "", " ") + data := bytes.NewBuffer(out) + + client := http.Client{Timeout: 5 * time.Second} + go client.Post(usageStatsURL, "application/json", data) +} + +func (uss *UsageStatsService) updateTotalStats() { + statsQuery := models.GetSystemStatsQuery{} + if err := uss.Bus.Dispatch(&statsQuery); err != nil { + metricsLogger.Error("Failed to get system stats", "error", err) + return + } + + metrics.M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards)) + metrics.M_StatTotal_Users.Set(float64(statsQuery.Result.Users)) + metrics.M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers)) + metrics.M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists)) + metrics.M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs)) +} + +func getEdition() string { + if setting.IsEnterprise { + return "enterprise" + } else { + return "oss" + } +} diff --git a/pkg/metrics/metrics_test.go b/pkg/infra/usagestats/usage_stats_test.go similarity index 94% rename from pkg/metrics/metrics_test.go rename to pkg/infra/usagestats/usage_stats_test.go index c27d6f64b8c..dd45e96f256 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -1,4 +1,4 @@ -package metrics +package usagestats import ( "bytes" @@ -21,8 +21,13 @@ import ( func TestMetrics(t *testing.T) { Convey("Test send usage stats", t, func() { + uss := &UsageStatsService{ + Bus: bus.New(), + } + var getSystemStatsQuery *models.GetSystemStatsQuery - bus.AddHandler("test", func(query *models.GetSystemStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetSystemStatsQuery) error { + query.Result = &models.SystemStats{ Dashboards: 1, Datasources: 2, @@ -44,7 +49,7 @@ func TestMetrics(t *testing.T) { }) var getDataSourceStatsQuery *models.GetDataSourceStatsQuery - bus.AddHandler("test", func(query *models.GetDataSourceStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetDataSourceStatsQuery) error { query.Result = []*models.DataSourceStats{ { Type: models.DS_ES, @@ -68,7 +73,7 @@ func TestMetrics(t *testing.T) { }) var getDataSourceAccessStatsQuery *models.GetDataSourceAccessStatsQuery - bus.AddHandler("test", func(query *models.GetDataSourceAccessStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetDataSourceAccessStatsQuery) error { query.Result = []*models.DataSourceAccessStats{ { Type: models.DS_ES, @@ -116,7 +121,7 @@ func TestMetrics(t *testing.T) { }) var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery - bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetAlertNotifierUsageStatsQuery) error { query.Result = []*models.NotifierUsageStats{ { Type: "slack", @@ -155,11 +160,11 @@ func TestMetrics(t *testing.T) { "grafana_com": true, } - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Given reporting not enabled and sending usage stats", func() { setting.ReportingEnabled = false - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Should not gather stats or call http endpoint", func() { So(getSystemStatsQuery, ShouldBeNil) @@ -179,7 +184,7 @@ func TestMetrics(t *testing.T) { setting.Packaging = "deb" wg.Add(1) - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Should gather stats and call http endpoint", func() { if waitTimeout(&wg, 2*time.Second) { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 326514a9687..718a63ee768 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -1,17 +1,8 @@ package metrics import ( - "bytes" - "encoding/json" - "net/http" "runtime" - "strings" - "time" - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" ) @@ -68,23 +59,6 @@ var ( grafanaBuildVersion *prometheus.GaugeVec ) -func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec { - counter := prometheus.NewCounterVec(opts, labels) - - for _, label := range labelValues { - counter.WithLabelValues(label).Add(0) - } - - return counter -} - -func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter { - counter := prometheus.NewCounter(opts) - counter.Add(0) - - return counter -} - func init() { M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{ Name: "instance_start_total", @@ -362,154 +336,19 @@ func initMetricVars() { } -func updateTotalStats() { - statsQuery := models.GetSystemStatsQuery{} - if err := bus.Dispatch(&statsQuery); err != nil { - metricsLogger.Error("Failed to get system stats", "error", err) - return +func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec { + counter := prometheus.NewCounterVec(opts, labels) + + for _, label := range labelValues { + counter.WithLabelValues(label).Add(0) } - M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards)) - M_StatTotal_Users.Set(float64(statsQuery.Result.Users)) - M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers)) - M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists)) - M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs)) + return counter } -var usageStatsURL = "https://stats.grafana.org/grafana-usage-report" +func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter { + counter := prometheus.NewCounter(opts) + counter.Add(0) -func getEdition() string { - if setting.IsEnterprise { - return "enterprise" - } else { - return "oss" - } -} - -func sendUsageStats(oauthProviders map[string]bool) { - if !setting.ReportingEnabled { - return - } - - metricsLogger.Debug("Sending anonymous usage stats to stats.grafana.org") - - version := strings.Replace(setting.BuildVersion, ".", "_", -1) - - metrics := map[string]interface{}{} - report := map[string]interface{}{ - "version": version, - "metrics": metrics, - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "edition": getEdition(), - "packaging": setting.Packaging, - } - - statsQuery := models.GetSystemStatsQuery{} - if err := bus.Dispatch(&statsQuery); err != nil { - metricsLogger.Error("Failed to get system stats", "error", err) - return - } - - metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards - metrics["stats.users.count"] = statsQuery.Result.Users - metrics["stats.orgs.count"] = statsQuery.Result.Orgs - metrics["stats.playlist.count"] = statsQuery.Result.Playlists - metrics["stats.plugins.apps.count"] = len(plugins.Apps) - metrics["stats.plugins.panels.count"] = len(plugins.Panels) - metrics["stats.plugins.datasources.count"] = len(plugins.DataSources) - metrics["stats.alerts.count"] = statsQuery.Result.Alerts - metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers - metrics["stats.datasources.count"] = statsQuery.Result.Datasources - metrics["stats.stars.count"] = statsQuery.Result.Stars - metrics["stats.folders.count"] = statsQuery.Result.Folders - metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions - metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions - metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards - metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots - metrics["stats.teams.count"] = statsQuery.Result.Teams - - dsStats := models.GetDataSourceStatsQuery{} - if err := bus.Dispatch(&dsStats); err != nil { - metricsLogger.Error("Failed to get datasource stats", "error", err) - return - } - - // send counters for each data source - // but ignore any custom data sources - // as sending that name could be sensitive information - dsOtherCount := 0 - for _, dsStat := range dsStats.Result { - if models.IsKnownDataSourcePlugin(dsStat.Type) { - metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count - } else { - dsOtherCount += dsStat.Count - } - } - metrics["stats.ds.other.count"] = dsOtherCount - - metrics["stats.packaging."+setting.Packaging+".count"] = 1 - - dsAccessStats := models.GetDataSourceAccessStatsQuery{} - if err := bus.Dispatch(&dsAccessStats); err != nil { - metricsLogger.Error("Failed to get datasource access stats", "error", err) - return - } - - // send access counters for each data source - // but ignore any custom data sources - // as sending that name could be sensitive information - dsAccessOtherCount := make(map[string]int64) - for _, dsAccessStat := range dsAccessStats.Result { - if dsAccessStat.Access == "" { - continue - } - - access := strings.ToLower(dsAccessStat.Access) - - if models.IsKnownDataSourcePlugin(dsAccessStat.Type) { - metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count - } else { - old := dsAccessOtherCount[access] - dsAccessOtherCount[access] = old + dsAccessStat.Count - } - } - - for access, count := range dsAccessOtherCount { - metrics["stats.ds_access.other."+access+".count"] = count - } - - anStats := models.GetAlertNotifierUsageStatsQuery{} - if err := bus.Dispatch(&anStats); err != nil { - metricsLogger.Error("Failed to get alert notification stats", "error", err) - return - } - - for _, stats := range anStats.Result { - metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count - } - - authTypes := map[string]bool{} - authTypes["anonymous"] = setting.AnonymousEnabled - authTypes["basic_auth"] = setting.BasicAuthEnabled - authTypes["ldap"] = setting.LdapEnabled - authTypes["auth_proxy"] = setting.AuthProxyEnabled - - for provider, enabled := range oauthProviders { - authTypes["oauth_"+provider] = enabled - } - - for authType, enabled := range authTypes { - enabledValue := 0 - if enabled { - enabledValue = 1 - } - metrics["stats.auth_enabled."+authType+".count"] = enabledValue - } - - out, _ := json.MarshalIndent(report, "", " ") - data := bytes.NewBuffer(out) - - client := http.Client{Timeout: 5 * time.Second} - go client.Post(usageStatsURL, "application/json", data) + return counter } diff --git a/pkg/metrics/service.go b/pkg/metrics/service.go index d2c0c815da9..44b83187cac 100644 --- a/pkg/metrics/service.go +++ b/pkg/metrics/service.go @@ -2,7 +2,6 @@ package metrics import ( "context" - "time" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics/graphitebridge" @@ -30,7 +29,6 @@ type InternalMetricsService struct { intervalSeconds int64 graphiteCfg *graphitebridge.Config - oauthProviders map[string]bool } func (im *InternalMetricsService) Init() error { @@ -50,22 +48,6 @@ func (im *InternalMetricsService) Run(ctx context.Context) error { M_Instance_Start.Inc() - // set the total stats gauges before we publishing metrics - updateTotalStats() - - onceEveryDayTick := time.NewTicker(time.Hour * 24) - everyMinuteTicker := time.NewTicker(time.Minute) - defer onceEveryDayTick.Stop() - defer everyMinuteTicker.Stop() - - for { - select { - case <-onceEveryDayTick.C: - sendUsageStats(im.oauthProviders) - case <-everyMinuteTicker.C: - updateTotalStats() - case <-ctx.Done(): - return ctx.Err() - } - } + <-ctx.Done() + return ctx.Err() } diff --git a/pkg/metrics/settings.go b/pkg/metrics/settings.go index 18b9e78d6ff..048e4134690 100644 --- a/pkg/metrics/settings.go +++ b/pkg/metrics/settings.go @@ -5,8 +5,6 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/social" - "github.com/grafana/grafana/pkg/metrics/graphitebridge" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" @@ -24,8 +22,6 @@ func (im *InternalMetricsService) readSettings() error { return fmt.Errorf("Unable to parse metrics graphite section, %v", err) } - im.oauthProviders = social.GetOAuthProviders(im.Cfg) - return nil } From e0809831470ab08153763d982e9aa68eba2f441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 7 Feb 2019 16:14:11 +0100 Subject: [PATCH 16/50] Panel edit navbar poc --- .../dashboard/components/DashNav/DashNav.tsx | 57 ++++++++++------ .../dashboard/containers/DashboardPage.tsx | 3 +- public/sass/components/_navbar.scss | 65 ++++++++++++++++++- public/sass/components/_panel_editor.scss | 4 ++ 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 297d7ca7ea7..8560b3bfbba 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -14,10 +14,11 @@ import { DashNavButton } from './DashNavButton'; import { updateLocation } from 'app/core/actions'; // Types -import { DashboardModel } from '../../state/DashboardModel'; +import { DashboardModel, PanelModel } from '../../state'; export interface Props { dashboard: DashboardModel; + fullscreenPanel?: PanelModel; editview: string; isEditing: boolean; isFullscreen: boolean; @@ -33,7 +34,6 @@ export class DashNav extends PureComponent { constructor(props: Props) { super(props); - this.playlistSrv = this.props.$injector.get('playlistSrv'); } @@ -123,16 +123,14 @@ export class DashNav extends PureComponent { }); }; - render() { - const { dashboard, isFullscreen, editview, onAddPanel } = this.props; - const { canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta; - const { snapshot } = dashboard; + renderDashboardTitleSearchButton() { + const { dashboard } = this.props; + const folderTitle = dashboard.meta.folderTitle; const haveFolder = dashboard.meta.folderId > 0; - const snapshotUrl = snapshot && snapshot.originalUrl; return ( -
+ <> -
+ + ); + } + + renderPanelFullscreeMode() { + const { fullscreenPanel } = this.props; + + return ( +
+ +
+ + +
+
+ ); + } + + render() { + const { dashboard, onAddPanel, fullscreenPanel } = this.props; + const { canStar, canSave, canShare, showSettings, isStarred } = dashboard.meta; + const { snapshot } = dashboard; + + const snapshotUrl = snapshot && snapshot.originalUrl; + + return ( +
+ {!fullscreenPanel && this.renderDashboardTitleSearchButton()} + {fullscreenPanel && this.renderPanelFullscreeMode()} {this.playlistSrv.isPlaying && (
@@ -228,17 +256,6 @@ export class DashNav extends PureComponent {
(this.timePickerEl = element)} /> - - {(isFullscreen || editview) && ( -
- -
- )}
); } diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 27118e297b5..724f3a625c0 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -238,7 +238,7 @@ export class DashboardPage extends PureComponent { render() { const { dashboard, editview, $injector, isInitSlow, initError } = this.props; - const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state; + const { isSettingsOpening, isEditing, isFullscreen, scrollTop, fullscreenPanel } = this.state; if (!dashboard) { if (isInitSlow) { @@ -266,6 +266,7 @@ export class DashboardPage extends PureComponent { editview={editview} $injector={$injector} onAddPanel={this.onAddPanel} + fullscreenPanel={fullscreenPanel} />
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 0cfa314a985..5215af41dcc 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -1,6 +1,6 @@ .navbar { position: relative; - padding-left: 40px; + padding-left: 20px; z-index: $zindex-navbar-fixed; height: $navbarHeight; padding-right: 20px; @@ -179,3 +179,66 @@ } } } + +.navbar-edit { + display: flex; + height: $navbarHeight; + align-items: center; + padding-left: 7px; + flex-grow: 1; +} + +.navbar-edit__back-btn { + background: transparent; + border: 2px solid $white; + border-radius: 50%; + width: 34px; + height: 34px; + margin-right: 7px; + + i { + font-size: $font-size-lg; + } +} + +.navbar-edit__input-wraper { + position: relative; + display: flex; + align-items: center; + flex-grow: 1; + + &:hover { + i { + opacity: 1; + } + + .navbar-edit__input { + background: $input-bg; + flex-grow: 1; + @include form-control-focus(); + } + } + + i { + left: -25px; + position: relative; + color: $text-color-weak; + opacity: 0; + transition: 200ms opacity ease-in-out; + } +} + +.navbar-edit__input { + background: transparent; + transition: 200ms background ease-in-out; + width: auto; + font-size: $font-size-lg; + height: $gf-form-input-height; + padding: $input-padding-y $input-padding-x; + flex-grow: 1; + + &:focus { + @include form-control-focus(); + background: $input-bg; + } +} diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index b791231a242..e533681d672 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -86,6 +86,10 @@ .panel-editor-container__panel { margin: 0 $dashboard-padding; } + + .panel-title-text { + visibility: hidden; + } } .panel-editor-container__resizer { From 2be60887cad49bfeaf37ed344e4981017f295c92 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 7 Feb 2019 16:27:40 +0100 Subject: [PATCH 17/50] adds usage stats for sessions --- pkg/infra/usagestats/service.go | 8 ++++---- pkg/infra/usagestats/usage_stats.go | 9 +++++++++ pkg/infra/usagestats/usage_stats_test.go | 8 +++++++- pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 3 ++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/infra/usagestats/service.go b/pkg/infra/usagestats/service.go index f853c03302d..c2bf0d06349 100644 --- a/pkg/infra/usagestats/service.go +++ b/pkg/infra/usagestats/service.go @@ -5,7 +5,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/log" @@ -20,9 +20,9 @@ func init() { } type UsageStatsService struct { - Cfg *setting.Cfg `inject:""` - TokenService *auth.UserAuthTokenService `inject:""` - Bus bus.Bus `inject:""` + Cfg *setting.Cfg `inject:""` + Bus bus.Bus `inject:""` + SQLStore *sqlstore.SqlStore `inject:""` oauthProviders map[string]bool } diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index b0dc52ccd8b..b54de124335 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -59,6 +59,15 @@ func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots metrics["stats.teams.count"] = statsQuery.Result.Teams + metrics["stats.total_sessions.count"] = statsQuery.Result.Sessions + + userCount := statsQuery.Result.Users + avgSessionsPerUser := statsQuery.Result.Sessions + if userCount != 0 { + avgSessionsPerUser = avgSessionsPerUser / userCount + } + + metrics["stats.avg_sessions_per_user.count"] = avgSessionsPerUser dsStats := models.GetDataSourceStatsQuery{} if err := uss.Bus.Dispatch(&dsStats); err != nil { diff --git a/pkg/infra/usagestats/usage_stats_test.go b/pkg/infra/usagestats/usage_stats_test.go index dd45e96f256..d343ed52b93 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -22,7 +23,8 @@ import ( func TestMetrics(t *testing.T) { Convey("Test send usage stats", t, func() { uss := &UsageStatsService{ - Bus: bus.New(), + Bus: bus.New(), + SQLStore: sqlstore.InitTestDB(t), } var getSystemStatsQuery *models.GetSystemStatsQuery @@ -43,6 +45,7 @@ func TestMetrics(t *testing.T) { ProvisionedDashboards: 12, Snapshots: 13, Teams: 14, + Sessions: 15, } getSystemStatsQuery = query return nil @@ -226,6 +229,8 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards) So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots) So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams) + So(metrics.Get("stats.total_sessions.count").MustInt64(), ShouldEqual, 15) + So(metrics.Get("stats.avg_sessions_per_user.count").MustInt64(), ShouldEqual, 5) So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9) So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10) @@ -251,6 +256,7 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1) So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1) + }) }) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index d3e145dedf4..00f881f3c59 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -15,6 +15,7 @@ type SystemStats struct { FolderPermissions int64 Folders int64 ProvisionedDashboards int64 + Sessions int64 } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 2cec86e7239..4c6d6c21221 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -74,7 +74,8 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`) - sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams,`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS sessions`) var stats m.SystemStats _, err := x.SQL(sb.GetSqlString(), sb.params...).Get(&stats) From df17f7dc459b156651d5bc27ee5aca27241aa242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 7 Feb 2019 18:20:16 +0100 Subject: [PATCH 18/50] fixed explore width-0 issue, fixes #15304 --- public/app/features/explore/Explore.tsx | 50 ++++++++++++++----------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 437b50db63c..a28776d813a 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -205,28 +205,34 @@ export class Explore extends React.PureComponent {
- {({ width }) => ( -
- - {showingStartPage && } - {!showingStartPage && ( - <> - {supportsGraph && !supportsLogs && } - {supportsTable && } - {supportsLogs && ( - - )} - - )} - -
- )} + {({ width }) => { + if (width === 0) { + return null; + } + + return ( +
+ + {showingStartPage && } + {!showingStartPage && ( + <> + {supportsGraph && !supportsLogs && } + {supportsTable && } + {supportsLogs && ( + + )} + + )} + +
+ ); + }}
)} From 0f96cf866272ef72a016b0d1c9b5225037d73a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 7 Feb 2019 19:06:51 +0100 Subject: [PATCH 19/50] slight tweaks --- public/sass/components/_navbar.scss | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 5215af41dcc..0096810798a 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -41,7 +41,7 @@ .panel-in-fullscreen { .navbar { - padding-left: 15px; + padding-left: 20px; } .navbar-button--add-panel, @@ -190,14 +190,22 @@ .navbar-edit__back-btn { background: transparent; - border: 2px solid $white; + border: 2px solid $text-color; border-radius: 50%; width: 34px; height: 34px; - margin-right: 7px; + transition: transform 0.1s ease 0.1s; + color: $text-color; i { font-size: $font-size-lg; + position: relative; + top: 2px; + } + + &:hover { + color: $text-color-strong; + border-color: $text-color-strong; } } From 13d9acb1ef8278e5ad704b785705da9ee51b3f3b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 13 Jan 2019 20:04:11 +0100 Subject: [PATCH 20/50] ldap: adds docker block for freeipa --- .gitignore | 1 + .../docker/blocks/freeipa/docker-compose.yaml | 54 ++++++++++++++ .../docker/blocks/freeipa/ldap_freeipa.toml | 74 +++++++++++++++++++ devenv/docker/blocks/freeipa/notes.md | 32 ++++++++ 4 files changed, 161 insertions(+) create mode 100644 devenv/docker/blocks/freeipa/docker-compose.yaml create mode 100644 devenv/docker/blocks/freeipa/ldap_freeipa.toml create mode 100644 devenv/docker/blocks/freeipa/notes.md diff --git a/.gitignore b/.gitignore index d599f762840..2945746832a 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ devenv/docker-compose.yaml /conf/provisioning/**/custom.yaml /conf/provisioning/**/dev.yaml /conf/ldap_dev.toml +/conf/ldap_freeipa.toml profile.cov /grafana /local diff --git a/devenv/docker/blocks/freeipa/docker-compose.yaml b/devenv/docker/blocks/freeipa/docker-compose.yaml new file mode 100644 index 00000000000..8a9a5705f9d --- /dev/null +++ b/devenv/docker/blocks/freeipa/docker-compose.yaml @@ -0,0 +1,54 @@ +version: '3' + +volumes: + freeipa_data: {} + +services: + freeipa: + image: freeipa/freeipa-server:fedora-29 + container_name: freeipa + stdin_open: true + tty: true + sysctls: + - net.ipv6.conf.all.disable_ipv6=0 + hostname: ipa.example.test + environment: + # - DEBUG_TRACE=1 + - IPA_SERVER_IP=172.17.0.2 + - DEBUG_NO_EXIT=1 + - IPA_SERVER_HOSTNAME=ipa.example.test + - PASSWORD=Secret123 + - HOSTNAME=ipa.example.test + command: + - --admin-password=Secret123 + - --ds-password=Secret123 + - -U + - --realm=EXAMPLE.TEST + ports: + # FreeIPA WebUI + - "80:80" + - "443:443" + # Kerberos + - "88:88/udp" + - "88:88" + - "464:464/udp" + - "464:464" + # LDAP + - "389:389" + - "636:636" + # DNS + # - "53:53/udp" + # - "53:53" + # NTP + - "123:123/udp" + # other + - "7389:7389" + - "9443:9443" + - "9444:9444" + - "9445:9445" + tmpfs: + - /run + - /tmp + volumes: + - freeipa_data:/data:Z + - /sys/fs/cgroup:/sys/fs/cgroup:ro diff --git a/devenv/docker/blocks/freeipa/ldap_freeipa.toml b/devenv/docker/blocks/freeipa/ldap_freeipa.toml new file mode 100644 index 00000000000..358b7cdebf9 --- /dev/null +++ b/devenv/docker/blocks/freeipa/ldap_freeipa.toml @@ -0,0 +1,74 @@ +# To troubleshoot and get more log info enable ldap debug logging in grafana.ini +# [log] +# filters = ldap:debug + +[[servers]] +# Ldap server host (specify multiple hosts space separated) +host = "172.17.0.1" +# Default port is 389 or 636 if use_ssl = true +port = 389 +# Set to true if ldap server supports TLS +use_ssl = false +# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS) +start_tls = false +# set to true if you want to skip ssl cert validation +ssl_skip_verify = false +# set to the path to your root CA certificate or leave unset to use system defaults +# root_ca_cert = "/path/to/certificate.crt" + +# Search user bind dn +bind_dn = "uid=admin,cn=users,cn=accounts,dc=example,dc=test" +# Search user bind password +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +bind_password = 'Secret123' + +# User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)" +search_filter = "(uid=%s)" + +# An array of base dns to search through +search_base_dns = ["cn=users,cn=accounts,dc=example,dc=test"] + +# In POSIX LDAP schemas, without memberOf attribute a secondary query must be made for groups. +# This is done by enabling group_search_filter below. You must also set member_of= "cn" +# in [servers.attributes] below. + +# Users with nested/recursive group membership and an LDAP server that supports LDAP_MATCHING_RULE_IN_CHAIN +# can set group_search_filter, group_search_filter_user_attribute, group_search_base_dns and member_of +# below in such a way that the user's recursive group membership is considered. +# +# Nested Groups + Active Directory (AD) Example: +# +# AD groups store the Distinguished Names (DNs) of members, so your filter must +# recursively search your groups for the authenticating user's DN. For example: +# +# group_search_filter = "(member:1.2.840.113556.1.4.1941:=%s)" +# group_search_filter_user_attribute = "distinguishedName" +# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"] +# +# [servers.attributes] +# ... +# member_of = "distinguishedName" + +## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available) +# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))" +## Group search filter user attribute defines what user attribute gets substituted for %s in group_search_filter. +## Defaults to the value of username in [server.attributes] +## Valid options are any of your values in [servers.attributes] +## If you are using nested groups you probably want to set this and member_of in +## [servers.attributes] to "distinguishedName" +# group_search_filter_user_attribute = "distinguishedName" +## An array of the base DNs to search through for groups. Typically uses ou=groups +# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"] + +# Specify names of the ldap attributes your ldap uses +[servers.attributes] +name = "givenName" +username = "uid" +member_of = "memberOf" +# surname = "sn" +# email = "mail" + +[[servers.group_mappings]] +# If you want to match all (or no ldap groups) then you can use wildcard +group_dn = "*" +org_role = "Viewer" diff --git a/devenv/docker/blocks/freeipa/notes.md b/devenv/docker/blocks/freeipa/notes.md new file mode 100644 index 00000000000..76afdf913c8 --- /dev/null +++ b/devenv/docker/blocks/freeipa/notes.md @@ -0,0 +1,32 @@ +# Notes on FreeIPA LDAP Docker Block + +Users have to be created manually. The docker-compose up command takes a few minutes to run. + +## Create a user + +`docker exec -it freeipa /bin/bash` + +To create a user with username: `ldap-viewer` and password: `grafana123` + +```bash +kinit admin +``` + +Log in with password `Secret123` + +```bash +ipa user-add ldap-viewer --first ldap --last viewer +ipa passwd ldap-viewer +ldappasswd -D uid=ldap-viewer,cn=users,cn=accounts,dc=example,dc=org -w test -a test -s grafana123 +``` + +## Enabling FreeIPA LDAP in Grafana + +Copy the ldap_freeipa.toml file in this folder into your `conf` folder (it is gitignored already). To enable it in the .ini file to get Grafana to use this block: + +```ini +[auth.ldap] +enabled = true +config_file = conf/ldap_freeipa.toml +; allow_sign_up = true +``` From 21a1507c7754c1028b3516831ac6989b9c99a485 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 13 Jan 2019 21:22:01 +0100 Subject: [PATCH 21/50] ldap: fixes #14432. Fix for IPA v4.6.4 IPA v4.6.4 introduced a fix that does not allow empty attributes to be sent in a search request. This fix only adds attributes to the request if they are mapped in the ldap toml file. --- pkg/login/ldap.go | 25 +++++++++++++++------- pkg/login/ldap_test.go | 48 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 402160ef5e3..7c45db95649 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -278,18 +278,27 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { var err error for _, searchBase := range a.server.SearchBaseDNs { + attributes := make([]string, 0) + + appendIfNotEmpty := func(slice []string, attr string) []string { + if attr == "" { + return slice + } + return append(slice, attr) + } + + attributes = appendIfNotEmpty(attributes, a.server.Attr.Username) + attributes = appendIfNotEmpty(attributes, a.server.Attr.Surname) + attributes = appendIfNotEmpty(attributes, a.server.Attr.Email) + attributes = appendIfNotEmpty(attributes, a.server.Attr.Name) + attributes = appendIfNotEmpty(attributes, a.server.Attr.MemberOf) + searchReq := ldap.SearchRequest{ BaseDN: searchBase, Scope: ldap.ScopeWholeSubtree, DerefAliases: ldap.NeverDerefAliases, - Attributes: []string{ - a.server.Attr.Username, - a.server.Attr.Surname, - a.server.Attr.Email, - a.server.Attr.Name, - a.server.Attr.MemberOf, - }, - Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1), + Attributes: attributes, + Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1), } a.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq)) diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index f1cf44dc554..c02fa02e030 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" "gopkg.in/ldap.v3" @@ -322,11 +323,51 @@ func TestLdapAuther(t *testing.T) { So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin") }) }) + + Convey("When searching for a user and not all five attributes are mapped", t, func() { + mockLdapConnection := &mockLdapConn{} + entry := ldap.Entry{ + DN: "dn", Attributes: []*ldap.EntryAttribute{ + {Name: "username", Values: []string{"roelgerrits"}}, + {Name: "surname", Values: []string{"Gerrits"}}, + {Name: "email", Values: []string{"roel@test.com"}}, + {Name: "name", Values: []string{"Roel"}}, + {Name: "memberof", Values: []string{"admins"}}, + }} + result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} + mockLdapConnection.setSearchResult(&result) + + // Set up attribute map without surname and email + ldapAuther := &ldapAuther{ + server: &LdapServerConf{ + Attr: LdapAttributeMap{ + Username: "username", + Name: "name", + MemberOf: "memberof", + }, + SearchBaseDNs: []string{"BaseDNHere"}, + }, + conn: mockLdapConnection, + log: log.New("test-logger"), + } + + searchResult, err := ldapAuther.searchForUser("roelgerrits") + + So(err, ShouldBeNil) + So(searchResult, ShouldNotBeNil) + + // User should be searched in ldap + So(mockLdapConnection.searchCalled, ShouldBeTrue) + + // No empty attributes should be added to the search request + So(len(mockLdapConnection.searchAttributes), ShouldEqual, 3) + }) } type mockLdapConn struct { - result *ldap.SearchResult - searchCalled bool + result *ldap.SearchResult + searchCalled bool + searchAttributes []string } func (c *mockLdapConn) Bind(username, password string) error { @@ -339,8 +380,9 @@ func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) { c.result = result } -func (c *mockLdapConn) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) { +func (c *mockLdapConn) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) { c.searchCalled = true + c.searchAttributes = sr.Attributes return c.result, nil } From b32d420a753a57c3e2c90e471655f81396dc0fc5 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Fri, 25 Jan 2019 16:43:54 +0100 Subject: [PATCH 22/50] ldap: refactoring. --- pkg/login/ldap.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 7c45db95649..c15cb865bd3 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -273,25 +273,28 @@ func (a *ldapAuther) initialBind(username, userPassword string) error { return nil } +func appendIfNotEmpty(slice []string, values ...string) []string { + for _, v := range values { + if v != "" { + slice = append(slice, v) + } + } + return slice +} + func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { var searchResult *ldap.SearchResult var err error for _, searchBase := range a.server.SearchBaseDNs { attributes := make([]string, 0) - - appendIfNotEmpty := func(slice []string, attr string) []string { - if attr == "" { - return slice - } - return append(slice, attr) - } - - attributes = appendIfNotEmpty(attributes, a.server.Attr.Username) - attributes = appendIfNotEmpty(attributes, a.server.Attr.Surname) - attributes = appendIfNotEmpty(attributes, a.server.Attr.Email) - attributes = appendIfNotEmpty(attributes, a.server.Attr.Name) - attributes = appendIfNotEmpty(attributes, a.server.Attr.MemberOf) + inputs := a.server.Attr + attributes = appendIfNotEmpty(attributes, + inputs.Username, + inputs.Surname, + inputs.Email, + inputs.Name, + inputs.MemberOf) searchReq := ldap.SearchRequest{ BaseDN: searchBase, From 71576a634e6057955087a862185f859ba04f705d Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 8 Feb 2019 14:06:06 +0100 Subject: [PATCH 23/50] Do not use js theme variables in sass (poor dev experience for now) --- package.json | 1 - .../grafana-ui/.storybook/webpack.config.js | 6 +- public/sass/_variables.dark.scss | 95 +++++++++---------- public/sass/_variables.light.scss | 90 ++++++++---------- public/sass/_variables.scss | 44 ++++----- scripts/webpack/getThemeVariable.js | 50 ---------- scripts/webpack/getThemeVariable.test.js | 40 -------- scripts/webpack/sass.rule.js | 6 +- scripts/webpack/webpack.hot.js | 8 +- yarn.lock | 5 - 10 files changed, 112 insertions(+), 233 deletions(-) delete mode 100644 scripts/webpack/getThemeVariable.js delete mode 100644 scripts/webpack/getThemeVariable.test.js diff --git a/package.json b/package.json index 41c319c7f82..5ac751ced3f 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "ng-annotate-webpack-plugin": "^0.3.0", "ngtemplate-loader": "^2.0.1", "node-sass": "^4.11.0", - "node-sass-utils": "^1.1.2", "npm": "^5.4.2", "optimize-css-assets-webpack-plugin": "^4.0.2", "phantomjs-prebuilt": "^2.1.15", diff --git a/packages/grafana-ui/.storybook/webpack.config.js b/packages/grafana-ui/.storybook/webpack.config.js index 4f27b71bb60..307a1142a7d 100644 --- a/packages/grafana-ui/.storybook/webpack.config.js +++ b/packages/grafana-ui/.storybook/webpack.config.js @@ -1,5 +1,4 @@ const path = require('path'); -const getThemeVariable = require('../../../scripts/webpack/getThemeVariable'); module.exports = (baseConfig, env, config) => { config.module.rules.push({ @@ -36,10 +35,7 @@ module.exports = (baseConfig, env, config) => { { loader: 'sass-loader', options: { - sourceMap: false, - functions: { - 'getThemeVariable($themeVar, $themeName: dark)': getThemeVariable, - }, + sourceMap: false }, }, ], diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 16d29ce41f7..149a1247b8e 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -3,69 +3,67 @@ $theme-name: dark; +// Grays // ------------------------- -$black: getThemeVariable('colors.black', $theme-name); -$dark-1: getThemeVariable('colors.dark1', $theme-name); -$dark-2: getThemeVariable('colors.dark2', $theme-name); -$dark-3: getThemeVariable('colors.dark3', $theme-name); -$dark-4: getThemeVariable('colors.dark4', $theme-name); -$dark-5: getThemeVariable('colors.dark5', $theme-name); -$gray-1: getThemeVariable('colors.gray1', $theme-name); -$gray-2: getThemeVariable('colors.gray2', $theme-name); -$gray-3: getThemeVariable('colors.gray3', $theme-name); -$gray-4: getThemeVariable('colors.gray4', $theme-name); -$gray-5: getThemeVariable('colors.gray5', $theme-name); -$gray-6: getThemeVariable('colors.gray6', $theme-name); -$gray-7: getThemeVariable('colors.gray7', $theme-name); +$black: #000; +$dark-1: #141414; +$dark-2: #1f1f20; +$dark-3: #262628; +$dark-4: #333333; +$dark-5: #444444; +$gray-1: #555555; +$gray-2: #8e8e8e; +$gray-3: #b3b3b3; +$gray-4: #d8d9da; +$gray-5: #ececec; +$gray-7: #fbfbfb; -$gray-blue: getThemeVariable('colors.grayBlue', $theme-name); -$input-black: getThemeVariable('colors.inputBlack', $theme-name); +$gray-blue: #212327; +$input-black: #09090b; -$white: getThemeVariable('colors.white', $theme-name); +$white: #fff; // Accent colors // ------------------------- -$blue: getThemeVariable('colors.blue', $theme-name); -$blue-dark: getThemeVariable('colors.blueDark', $theme-name); -$green: getThemeVariable('colors.green', $theme-name); -$red: getThemeVariable('colors.red', $theme-name); -$yellow: getThemeVariable('colors.yellow', $theme-name); -$pink: getThemeVariable('colors.pink', $theme-name); -$purple: getThemeVariable('colors.purple', $theme-name); -$variable: getThemeVariable('colors.variable', $theme-name); -$orange: getThemeVariable('colors.orange', $theme-name); +$blue: #33b5e5; +$blue-dark: #005f81; +$green: #299c46; +$red: #d44a3a; +$yellow: #ecbb13; +$purple: #9933cc; +$variable: #32d1df; +$orange: #eb7b18; $brand-primary: $orange; $brand-success: $green; $brand-warning: $brand-primary; $brand-danger: $red; -$query-red: getThemeVariable('colors.queryRed', $theme-name); -$query-green: getThemeVariable('colors.queryGreen', $theme-name); -$query-purple: getThemeVariable('colors.queryPurple', $theme-name); -$query-keyword: getThemeVariable('colors.queryKeyword', $theme-name); -$query-orange: getThemeVariable('colors.queryOrange', $theme-name); +$query-red: #e24d42; +$query-green: #74e680; +$query-purple: #fe85fc; +$query-keyword: #66d9ef; +$query-orange: $orange; // Status colors // ------------------------- -$online: getThemeVariable('colors.online', $theme-name); -$warn: getThemeVariable('colors.warn', $theme-name); -$critical: getThemeVariable('colors.critical', $theme-name); +$online: #10a345; +$warn: #f79520; +$critical: #ed2e18; // Scaffolding // ------------------------- -$body-bg: getThemeVariable('colors.bodyBg', $theme-name); -$page-bg: getThemeVariable('colors.pageBg', $theme-name); +$body-bg: rgb(23, 24, 25); +$page-bg: rgb(22, 23, 25); -$body-color: getThemeVariable('colors.bodyColor', $theme-name); -$text-color: getThemeVariable('colors.textColor', $theme-name); -$text-color-strong: getThemeVariable('colors.textColorStrong', $theme-name); -$text-color-weak: getThemeVariable('colors.textColorWeak', $theme-name); -$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name); -$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name); +$body-color: $gray-4; +$text-color: $gray-4; +$text-color-strong: $white; +$text-color-weak: $gray-2; +$text-color-faint: $dark-5; +$text-color-emphasis: $gray-5; -$text-shadow-strong: 1px 1px 4px getThemeVariable('colors.black', $theme-name); -$text-shadow-faint: 1px 1px 4px #2d2d2d; +$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45); $textShadow: none; // gradients @@ -81,11 +79,10 @@ $edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); // Links // ------------------------- -$link-color: getThemeVariable('colors.linkColor', $theme-name); -$link-color-disabled: getThemeVariable('colors.linkColorDisabled', $theme-name); -$link-hover-color: getThemeVariable('colors.linkColorHover', $theme-name); - -$external-link-color: getThemeVariable('colors.linkColorExternal', $theme-name); +$link-color: darken($white, 11%); +$link-color-disabled: darken($link-color, 30%); +$link-hover-color: $white; +$external-link-color: $blue; // Typography // ------------------------- @@ -132,7 +129,7 @@ $list-item-shadow: $card-shadow; $empty-list-cta-bg: $gray-blue; // Scrollbars -$scrollbarBackground: #aeb5df; +$scrollbarBackground: #404357; $scrollbarBackground2: #3a3a3a; $scrollbarBorder: black; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index a4e4e806a68..97d7a374765 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -6,66 +6,59 @@ $theme-name: light; // Grays // ------------------------- $black: #000; +$dark-2: #1e2028; +$dark-3: #303133; +$dark-4: #35373f; +$dark-5: #41444b; +$gray-1: #52545c; +$gray-2: #767980; +$gray-3: #acb6bf; +$gray-4: #c7d0d9; +$gray-5: #dde4ed; +$gray-6: #e9edf2; +$gray-7: #f7f8fa; -// ------------------------- -$black: getThemeVariable('colors.black', $theme-name); -$dark-1: getThemeVariable('colors.dark1', $theme-name); -$dark-2: getThemeVariable('colors.dark2', $theme-name); -$dark-3: getThemeVariable('colors.dark3', $theme-name); -$dark-4: getThemeVariable('colors.dark4', $theme-name); -$dark-5: getThemeVariable('colors.dark5', $theme-name); -$gray-1: getThemeVariable('colors.gray1', $theme-name); -$gray-2: getThemeVariable('colors.gray2', $theme-name); -$gray-3: getThemeVariable('colors.gray3', $theme-name); -$gray-4: getThemeVariable('colors.gray4', $theme-name); -$gray-5: getThemeVariable('colors.gray5', $theme-name); -$gray-6: getThemeVariable('colors.gray6', $theme-name); -$gray-7: getThemeVariable('colors.gray7', $theme-name); - -$white: getThemeVariable('colors.white', $theme-name); +$white: #fff; // Accent colors // ------------------------- -$blue: getThemeVariable('colors.blue', $theme-name); -$blue-dark: getThemeVariable('colors.blueDark', $theme-name); -$blue-light: getThemeVariable('colors.blueLight', $theme-name); -$green: getThemeVariable('colors.green', $theme-name); -$red: getThemeVariable('colors.red', $theme-name); -$yellow: getThemeVariable('colors.yellow', $theme-name); -$orange: getThemeVariable('colors.orange', $theme-name); -$pink: getThemeVariable('colors.pink', $theme-name); -$purple: getThemeVariable('colors.purple', $theme-name); -$variable: getThemeVariable('colors.variable', $theme-name); +$blue: #0083b3; +$blue-light: #00a8e6; +$green: #3aa655; +$red: #d44939; +$yellow: #ff851b; +$orange: #ff7941; +$purple: #9954bb; +$variable: $blue; $brand-primary: $orange; $brand-success: $green; $brand-warning: $orange; $brand-danger: $red; -$query-red: getThemeVariable('colors.queryRed', $theme-name); -$query-green: getThemeVariable('colors.queryGreen', $theme-name); -$query-purple: getThemeVariable('colors.queryPurple', $theme-name); -$query-keyword: getThemeVariable('colors.queryKeyword', $theme-name); -$query-orange: getThemeVariable('colors.queryOrange', $theme-name); +$query-red: $red; +$query-green: $green; +$query-purple: $purple; +$query-orange: $orange; +$query-keyword: $blue; // Status colors // ------------------------- -$online: getThemeVariable('colors.online', $theme-name); -$warn: getThemeVariable('colors.warn', $theme-name); -$critical: getThemeVariable('colors.critical', $theme-name); +$online: #01a64f; +$warn: #f79520; +$critical: #ec2128; // Scaffolding // ------------------------- +$body-bg: $gray-7; +$page-bg: $gray-7; -$body-bg: getThemeVariable('colors.bodyBg', $theme-name); -$page-bg: getThemeVariable('colors.pageBg', $theme-name); - -$body-color: getThemeVariable('colors.bodyColor', $theme-name); -$text-color: getThemeVariable('colors.textColor', $theme-name); -$text-color-strong: getThemeVariable('colors.textColorStrong', $theme-name); -$text-color-weak: getThemeVariable('colors.textColorWeak', $theme-name); -$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name); -$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name); +$body-color: $gray-1; +$text-color: $gray-1; +$text-color-strong: $dark-2; +$text-color-weak: $gray-2; +$text-color-faint: $gray-4; +$text-color-emphasis: $dark-5; $text-shadow-faint: none; $textShadow: none; @@ -83,15 +76,14 @@ $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%); // Links // ------------------------- -$link-color: getThemeVariable('colors.linkColor', $theme-name); -$link-color-disabled: getThemeVariable('colors.linkColorDisabled', $theme-name); -$link-hover-color: getThemeVariable('colors.linkColorHover', $theme-name); - -$external-link-color: getThemeVariable('colors.linkColorExternal', $theme-name); +$link-color: $gray-1; +$link-color-disabled: lighten($link-color, 30%); +$link-hover-color: darken($link-color, 20%); +$external-link-color: $blue-light; // Typography // ------------------------- -$headings-color: getThemeVariable('colors.headingColor', $theme-name); +$headings-color: $text-color; $abbr-border-color: $gray-2 !default; $text-muted: $text-color-weak; diff --git a/public/sass/_variables.scss b/public/sass/_variables.scss index eab0e8c7f5a..4e9e69c4d2f 100644 --- a/public/sass/_variables.scss +++ b/public/sass/_variables.scss @@ -47,45 +47,45 @@ $enable-flex: true; // Typography // ------------------------- -$font-family-sans-serif: getThemeVariable('typography.fontFamily.sansSerif'); -$font-family-serif: getThemeVariable('typography.fontFamily.serif'); -$font-family-monospace: getThemeVariable('typography.fontFamily.monospace'); +$font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif; +$font-family-serif: Georgia, 'Times New Roman', Times, serif; +$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace; $font-family-base: $font-family-sans-serif !default; -$font-size-root: getThemeVariable('typography.size.m') !default; -$font-size-base: getThemeVariable('typography.size.base') !default; +$font-size-root: 14px !default; +$font-size-base: 13px !default; -$font-size-lg: getThemeVariable('typography.size.l') !default; -$font-size-md: getThemeVariable('typography.size.m') !default; -$font-size-sm: getThemeVariable('typography.size.s') !default; -$font-size-xs: getThemeVariable('typography.size.xs') !default; +$font-size-lg: 18px !default; +$font-size-md: 14px !default; +$font-size-sm: 12px !default; +$font-size-xs: 10px !default; -$line-height-base: getThemeVariable('typography.lineHeight.l') !default; -$font-weight-semi-bold: getThemeVariable('typography.weight.semibold'); +$line-height-base: 1.5 !default; +$font-weight-semi-bold: 500; -$font-size-h1: getThemeVariable('typography.heading.h1') !default; -$font-size-h2: getThemeVariable('typography.heading.h2') !default; -$font-size-h3: getThemeVariable('typography.heading.h3') !default; -$font-size-h4: getThemeVariable('typography.heading.h4') !default; -$font-size-h5: getThemeVariable('typography.heading.h5') !default; -$font-size-h6: getThemeVariable('typography.heading.h6') !default; +$font-size-h1: 2rem !default; +$font-size-h2: 1.75rem !default; +$font-size-h3: 1.5rem !default; +$font-size-h4: 1.3rem !default; +$font-size-h5: 1.2rem !default; +$font-size-h6: 1rem !default; $display1-size: 6rem !default; $display2-size: 5.5rem !default; $display3-size: 4.5rem !default; $display4-size: 3.5rem !default; -$display1-weight: getThemeVariable('typography.weight.normal') !default; -$display2-weight: getThemeVariable('typography.weight.normal') !default; -$display3-weight: getThemeVariable('typography.weight.normal') !default; -$display4-weight: getThe1meVariable('typography.weight.normal') !default; +$display1-weight: 400 !default; +$display2-weight: 400 !default; +$display3-weight: 400 !default; +$display4-weight: 400 !default; $lead-font-size: 1.25rem !default; $lead-font-weight: 300 !default; $headings-margin-bottom: ($spacer / 2) !default; $headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; -$headings-font-weight: getThemeVariable('typography.weight.normal') !default; +$headings-font-weight: 400 !default; $headings-line-height: 1.1 !default; $hr-border-width: $border-width !default; diff --git a/scripts/webpack/getThemeVariable.js b/scripts/webpack/getThemeVariable.js deleted file mode 100644 index 3bd9b2a53d0..00000000000 --- a/scripts/webpack/getThemeVariable.js +++ /dev/null @@ -1,50 +0,0 @@ -const sass = require('node-sass'); -const sassUtils = require('node-sass-utils')(sass); -const { get } = require('lodash'); -const tinycolor = require('tinycolor2'); -const { getTheme } = require('@grafana/ui/src/themes'); - -const units = ['rem', 'em', 'vh', 'vw', 'vmin', 'vmax', 'ex', '%', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ch']; -const matchDimension = value => value.match(/[a-zA-Z]+|[0-9]+/g); - -const isHex = value => { - const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6})$/gi; - return hexRegex.test(value); -}; - -const isDimension = value => { - if (typeof value !== 'string') { - return false; - } - const [val, unit] = matchDimension(value); - return units.indexOf(unit) > -1; -}; - -/** - * @param {SassString} variablePath - * @param {"dark"|"light"} themeName - */ -function getThemeVariable(variablePath, themeName) { - const theme = getTheme(themeName.getValue()); - const variable = get(theme, variablePath.getValue()); - - if (!variable) { - throw new Error(`${variablePath.getValue()} is not defined for ${themeName.getValue()}`); - } - - if (isHex(variable)) { - const rgb = new tinycolor(variable).toRgb(); - const color = new sass.types.Color(rgb.r, rgb.g, rgb.b); - return color; - } - - if (isDimension(variable)) { - const [value, unit] = matchDimension(variable); - const dimension = new sassUtils.SassDimension(parseInt(value, 10), unit); - return sassUtils.castToSass(dimension); - } - - return sassUtils.castToSass(variable); -} - -module.exports = getThemeVariable; diff --git a/scripts/webpack/getThemeVariable.test.js b/scripts/webpack/getThemeVariable.test.js deleted file mode 100644 index 57a6fb5236c..00000000000 --- a/scripts/webpack/getThemeVariable.test.js +++ /dev/null @@ -1,40 +0,0 @@ -const sass = require('node-sass'); -const getThemeVariable = require('./getThemeVariable'); -const { mockTheme } = require('@grafana/ui'); - -const themeMock = { - color: { - background: '#ff0000', - }, - spacing: { - padding: '2em', - }, - typography: { - fontFamily: 'Arial, sans-serif', - }, -}; - -describe('Variables retrieval', () => { - const restoreTheme = mockTheme(() => themeMock); - - afterAll(() => { - restoreTheme(); - }); - - it('returns sass Color for color values', () => { - const result = getThemeVariable({ getValue: () => 'color.background' }, { getValue: () => {} }); - expect(result).toBeInstanceOf(sass.types.Color); - }); - it('returns sass Number for dimension values', () => { - const result = getThemeVariable({ getValue: () => 'spacing.padding' }, { getValue: () => {} }); - expect(result).toBeInstanceOf(sass.types.Number); - }); - it('returns sass String for string values', () => { - const result = getThemeVariable({ getValue: () => 'typography.fontFamily' }, { getValue: () => {} }); - expect(result).toBeInstanceOf(sass.types.String); - }); - - it('throws for unknown theme paths', () => { - expect(() => getThemeVariable({ getValue: () => 'what.ever' }, { getValue: () => {} })).toThrow(); - }); -}); diff --git a/scripts/webpack/sass.rule.js b/scripts/webpack/sass.rule.js index 78f6b60d33f..66a48a12b32 100644 --- a/scripts/webpack/sass.rule.js +++ b/scripts/webpack/sass.rule.js @@ -1,7 +1,6 @@ 'use strict'; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const getThemeVariable = require('./getThemeVariable'); module.exports = function(options) { return { @@ -27,10 +26,7 @@ module.exports = function(options) { { loader: 'sass-loader', options: { - sourceMap: options.sourceMap, - functions: { - 'getThemeVariable($themeVar, $themeName: dark)': getThemeVariable, - }, + sourceMap: options.sourceMap }, }, ], diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index 4519e292c6b..c1053f1f7da 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -8,7 +8,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const IgnoreNotFoundExportPlugin = require("./IgnoreNotFoundExportPlugin.js"); -const getThemeVariable = require("./getThemeVariable"); module.exports = merge(common, { entry: { @@ -87,12 +86,7 @@ module.exports = merge(common, { }, }, { - loader: 'sass-loader', - options: { - functions: { - "getThemeVariable($themeVar, $themeName: dark)": getThemeVariable - } - } + loader: 'sass-loader' } ], }, diff --git a/yarn.lock b/yarn.lock index cd3dfbbebd8..df2e1cea37e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11910,11 +11910,6 @@ node-releases@^1.0.0-alpha.11, node-releases@^1.1.3: dependencies: semver "^5.3.0" -node-sass-utils@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/node-sass-utils/-/node-sass-utils-1.1.2.tgz#d03639cfa4fc962398ba3648ab466f0db7cc2131" - integrity sha1-0DY5z6T8liOYujZIq0ZvDbfMITE= - node-sass@^4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" From 7e03913d0d3c95cf2fa441d2f21648b4841e9f1a Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 8 Feb 2019 14:06:53 +0100 Subject: [PATCH 24/50] Use TS instead of JS to store theme variables@next --- .../src/themes/{dark.js => dark.ts} | 11 ++++++----- .../src/themes/{default.js => default.ts} | 2 +- packages/grafana-ui/src/themes/index.d.ts | 4 ---- packages/grafana-ui/src/themes/index.js | 16 ---------------- packages/grafana-ui/src/themes/index.ts | 14 ++++++++++++++ .../src/themes/{light.js => light.ts} | 19 +++++++++---------- .../src/themes/selectThemeVariant.test.ts | 1 + 7 files changed, 31 insertions(+), 36 deletions(-) rename packages/grafana-ui/src/themes/{dark.js => dark.ts} (87%) rename packages/grafana-ui/src/themes/{default.js => default.ts} (97%) delete mode 100644 packages/grafana-ui/src/themes/index.d.ts delete mode 100644 packages/grafana-ui/src/themes/index.js create mode 100644 packages/grafana-ui/src/themes/index.ts rename packages/grafana-ui/src/themes/{light.js => light.ts} (86%) diff --git a/packages/grafana-ui/src/themes/dark.js b/packages/grafana-ui/src/themes/dark.ts similarity index 87% rename from packages/grafana-ui/src/themes/dark.js rename to packages/grafana-ui/src/themes/dark.ts index d5e7e5aacba..deae022f63a 100644 --- a/packages/grafana-ui/src/themes/dark.js +++ b/packages/grafana-ui/src/themes/dark.ts @@ -1,5 +1,6 @@ -const defaultTheme = require('./default'); -const tinycolor = require('tinycolor2'); +import tinycolor from 'tinycolor2'; +import defaultTheme from './default'; +import { GrafanaTheme, GrafanaThemeType } from '../types/theme'; const basicColors = { black: '#000000', @@ -29,9 +30,9 @@ const basicColors = { orange: '#eb7b18', }; -const darkTheme = { +const darkTheme: GrafanaTheme = { ...defaultTheme, - type: 'dark', + type: GrafanaThemeType.Dark, name: 'Grafana Dark', colors: { ...basicColors, @@ -65,4 +66,4 @@ const darkTheme = { }, }; -module.exports = darkTheme; +export default darkTheme; diff --git a/packages/grafana-ui/src/themes/default.js b/packages/grafana-ui/src/themes/default.ts similarity index 97% rename from packages/grafana-ui/src/themes/default.js rename to packages/grafana-ui/src/themes/default.ts index d95c6ad5689..bf318f526e7 100644 --- a/packages/grafana-ui/src/themes/default.js +++ b/packages/grafana-ui/src/themes/default.ts @@ -59,4 +59,4 @@ const theme = { } }; -module.exports = theme; +export default theme; diff --git a/packages/grafana-ui/src/themes/index.d.ts b/packages/grafana-ui/src/themes/index.d.ts deleted file mode 100644 index c16d489d9e0..00000000000 --- a/packages/grafana-ui/src/themes/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { GrafanaTheme } from "../types"; - -export function getTheme(themeName?: string): GrafanaTheme -export function mockTheme(themeMock: (name: string) => object): () => void diff --git a/packages/grafana-ui/src/themes/index.js b/packages/grafana-ui/src/themes/index.js deleted file mode 100644 index 21dd19cf119..00000000000 --- a/packages/grafana-ui/src/themes/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const darkTheme = require('./dark'); -const lightTheme = require('./light'); - -let mockedTheme; - -let getTheme = name => (mockedTheme && mockedTheme(name)) || (name === 'light' ? lightTheme : darkTheme); - -const mockTheme = mock => { - mockedTheme = mock; - return () => (mockedTheme = null); -}; - -module.exports = { - getTheme, - mockTheme, -}; diff --git a/packages/grafana-ui/src/themes/index.ts b/packages/grafana-ui/src/themes/index.ts new file mode 100644 index 00000000000..c0d9a4f2d32 --- /dev/null +++ b/packages/grafana-ui/src/themes/index.ts @@ -0,0 +1,14 @@ +import darkTheme from './dark'; +import lightTheme from './light'; +import { GrafanaTheme } from '../types/theme'; + +let themeMock: ((name?: string) => GrafanaTheme) | null; + +export let getTheme = (name?: string) => (themeMock && themeMock(name)) || (name === 'light' ? lightTheme : darkTheme); + +export const mockTheme = (mock: (name: string) => GrafanaTheme) => { + themeMock = mock; + return () => { + themeMock = null; + }; +}; diff --git a/packages/grafana-ui/src/themes/light.js b/packages/grafana-ui/src/themes/light.ts similarity index 86% rename from packages/grafana-ui/src/themes/light.js rename to packages/grafana-ui/src/themes/light.ts index 8da6190caba..fd1f1d05b95 100644 --- a/packages/grafana-ui/src/themes/light.js +++ b/packages/grafana-ui/src/themes/light.ts @@ -1,7 +1,6 @@ -// import { GrafanaThemeType } from "../theme"; - -const defaultTheme = require('./default'); -const tinycolor = require('tinycolor2'); +import tinycolor from 'tinycolor2'; +import defaultTheme from './default'; +import { GrafanaTheme, GrafanaThemeType } from '../types/theme'; const basicColors = { black: '#000000', @@ -31,11 +30,11 @@ const basicColors = { orange: '#ff7941', }; -const lightTheme/*: GrafanaThemeType*/ = { +const lightTheme: GrafanaTheme = { ...defaultTheme, - type: 'light', + type: GrafanaThemeType.Light, name: 'Grafana Light', - colors: { + colors: { ...basicColors, variable: basicColors.blue, inputBlack: '#09090b', @@ -65,7 +64,7 @@ const lightTheme/*: GrafanaThemeType*/ = { dropdown: basicColors.white, scrollbar: basicColors.gray5, scrollbar2: basicColors.gray5, - } -} + }, +}; -module.exports = lightTheme; +export default lightTheme; diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts index 86e35f515c2..66cb02a2372 100644 --- a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts +++ b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts @@ -17,6 +17,7 @@ const darkThemeMock = { }; describe('Theme variable variant selector', () => { + // @ts-ignore const restoreTheme = mockTheme(name => (name === GrafanaThemeType.Light ? lightThemeMock : darkThemeMock)); afterAll(() => { From 5436c284481bd98ae63e24ddd82c7e1d428dc878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 8 Feb 2019 15:38:45 +0100 Subject: [PATCH 25/50] Minor refactoring around theme access --- jest.config.js | 2 - public/app/core/config.ts | 4 ++ .../panel/graph/Legend/LegendSeriesItem.tsx | 1 - .../app/plugins/panel/graph/data_processor.ts | 4 +- public/app/plugins/panel/graph/graph.ts | 7 +--- public/app/plugins/panel/graph/module.ts | 4 +- public/app/plugins/panel/singlestat/module.ts | 10 +---- public/app/plugins/panel/table/module.ts | 3 +- scripts/webpack/webpack.test.js | 38 ------------------- 9 files changed, 13 insertions(+), 60 deletions(-) delete mode 100644 scripts/webpack/webpack.test.js diff --git a/jest.config.js b/jest.config.js index 248435c43f2..c5c6bcb9f5f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,13 +6,11 @@ module.exports = { }, "moduleDirectories": ["node_modules", "public"], "roots": [ - "/scripts", "/public/app", "/public/test", "/packages" ], "testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$", - "testPathIgnorePatterns": ["webpack.test.js"], "moduleFileExtensions": [ "ts", "tsx", diff --git a/public/app/core/config.ts b/public/app/core/config.ts index 368b3798117..f4254ac251a 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import { PanelPlugin } from 'app/types/plugins'; +import { GrafanaTheme, getTheme, GrafanaThemeType } from '@grafana/ui'; export interface BuildInfo { version: string; @@ -36,8 +37,11 @@ export class Settings { loginError: any; viewersCanEdit: boolean; disableSanitizeHtml: boolean; + theme: GrafanaTheme; constructor(options: Settings) { + this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark); + const defaults = { datasources: {}, windowTitlePrefix: 'Grafana - ', diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx index e3de5b067ba..2cf45727c4a 100644 --- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx +++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { TimeSeries } from 'app/core/core'; import { SeriesColorPicker } from '@grafana/ui'; -// import { ThemeProvider } from 'app/core/utils/ConfigProvider'; export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total']; diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index 0d4445e1981..2966bb33eb4 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { colors, GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; +import { colors, getColorFromHexRgbOrName } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; import config from 'app/core/config'; @@ -113,7 +113,7 @@ export class DataProcessor { const series = new TimeSeries({ datapoints: datapoints, alias: alias, - color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark), + color: getColorFromHexRgbOrName(color, config.theme.type), unit: seriesData.unit, }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 846d11ea475..54ba4ed1e6f 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -25,7 +25,7 @@ import ReactDOM from 'react-dom'; import { Legend, GraphLegendProps } from './Legend/Legend'; import { GraphCtrl } from './module'; -import { GrafanaThemeType, getValueFormat } from '@grafana/ui'; +import { getValueFormat } from '@grafana/ui'; import { provideTheme } from 'app/core/utils/ConfigProvider'; const LegendWithThemeProvider = provideTheme(Legend); @@ -55,10 +55,7 @@ class GraphElement { this.panelWidth = 0; this.eventManager = new EventManager(this.ctrl); this.thresholdManager = new ThresholdManager(this.ctrl); - this.timeRegionManager = new TimeRegionManager( - this.ctrl, - config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark - ); + this.timeRegionManager = new TimeRegionManager(this.ctrl, config.theme.type); this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => { return this.sortedSeries; }); diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index cb1c0d98269..3919c4f69a9 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -10,7 +10,7 @@ import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { DataProcessor } from './data_processor'; import { axesEditorComponent } from './axes_editor'; import config from 'app/core/config'; -import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; +import { getColorFromHexRgbOrName } from '@grafana/ui'; class GraphCtrl extends MetricsPanelCtrl { static template = template; @@ -244,7 +244,7 @@ class GraphCtrl extends MetricsPanelCtrl { } onColorChange = (series, color) => { - series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark)); + series.setColor(getColorFromHexRgbOrName(color, config.theme.type)); this.panel.aliasColors[series.alias] = color; this.render(); }; diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 4ea81ff8630..21ab32278f8 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -588,10 +588,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { fill: 1, zero: false, lineWidth: 1, - fillColor: getColorFromHexRgbOrName( - panel.sparkline.fillColor, - config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark - ), + fillColor: getColorFromHexRgbOrName(panel.sparkline.fillColor, config.theme.type), }, }, yaxes: { show: false }, @@ -608,10 +605,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { const plotSeries = { data: data.flotpairs, - color: getColorFromHexRgbOrName( - panel.sparkline.lineColor, - config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark - ), + color: getColorFromHexRgbOrName(panel.sparkline.lineColor, config.theme.type), }; $.plot(plotCanvas, [plotSeries], options); diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 3d82dd4df68..268f5aa7ac4 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -6,7 +6,6 @@ import { transformDataToTable } from './transformers'; import { tablePanelEditor } from './editor'; import { columnOptionsTab } from './column_options'; import { TableRenderer } from './renderer'; -import { GrafanaThemeType } from '@grafana/ui'; class TablePanelCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -131,7 +130,7 @@ class TablePanelCtrl extends MetricsPanelCtrl { this.dashboard.isTimezoneUtc(), this.$sanitize, this.templateSrv, - config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark, + config.theme.type ); return super.render(this.table); diff --git a/scripts/webpack/webpack.test.js b/scripts/webpack/webpack.test.js deleted file mode 100644 index ec9ee5e26df..00000000000 --- a/scripts/webpack/webpack.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const common = require('./webpack.common.js'); - -config = merge(common, { - mode: 'development', - devtool: 'cheap-module-source-map', - - externals: { - 'react/addons': true, - 'react/lib/ExecutionEnvironment': true, - 'react/lib/ReactContext': true, - }, - - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /node_modules/, - use: { - loader: 'ts-loader', - options: { - transpileOnly: true, - }, - }, - }, - ], - }, - - plugins: [ - new webpack.SourceMapDevToolPlugin({ - filename: null, // if no value is provided the sourcemap is inlined - test: /\.(ts|js)($|\?)/i, // process .js and .ts files only - }), - ], -}); - -module.exports = config; From 3d5ae3dca34b1c1fa3281293ee4348454a07c99d Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 8 Feb 2019 08:10:02 -0800 Subject: [PATCH 26/50] mark packages as Apache license --- packages/grafana-build/package.json | 4 ++-- packages/grafana-ui/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grafana-build/package.json b/packages/grafana-build/package.json index 24fb648c8d4..056e5d2c7ea 100644 --- a/packages/grafana-build/package.json +++ b/packages/grafana-build/package.json @@ -8,6 +8,6 @@ "tslint": "echo \"Nothing to do\"", "typecheck": "echo \"Nothing to do\"" }, - "author": "", - "license": "ISC" + "author": "Grafana Labs", + "license": "Apache-2.0" } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 0d1b14a7150..a0c76f711af 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -8,8 +8,8 @@ "typecheck": "tsc --noEmit", "storybook": "start-storybook -p 9001 -c .storybook -s ../../public" }, - "author": "", - "license": "ISC", + "author": "Grafana Labs", + "license": "Apache-2.0", "dependencies": { "@torkelo/react-select": "2.1.1", "@types/react-color": "^2.14.0", From 1bc2a0af70304bee4b4a18beb5865c604cf2a942 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 8 Feb 2019 18:08:07 +0100 Subject: [PATCH 27/50] use unique datasource id when registering mysql tls config --- pkg/tsdb/mysql/mysql.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index d451150f1de..d307e12166c 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -39,8 +39,9 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin } if tlsConfig.RootCAs != nil || len(tlsConfig.Certificates) > 0 { - mysql.RegisterTLSConfig(datasource.Name, tlsConfig) - cnnstr += "&tls=" + datasource.Name + tlsConfigString := fmt.Sprintf("ds%d", datasource.Id) + mysql.RegisterTLSConfig(tlsConfigString, tlsConfig) + cnnstr += "&tls=" + tlsConfigString } logger.Debug("getEngine", "connection", cnnstr) From 169732997ddc38ea9b885bdbbb654ffbda8391b6 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 8 Feb 2019 12:46:30 -0800 Subject: [PATCH 28/50] support three letter hex color strings --- packages/grafana-ui/src/utils/namedColorsPalette.test.ts | 2 ++ packages/grafana-ui/src/utils/namedColorsPalette.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index f875b9966f1..aa57b46636c 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -59,6 +59,8 @@ describe('colors', () => { it('returns color if specified as hex or rgb/a', () => { expect(getColorFromHexRgbOrName('ff0000')).toBe('ff0000'); expect(getColorFromHexRgbOrName('#ff0000')).toBe('#ff0000'); + expect(getColorFromHexRgbOrName('#FF0000')).toBe('#FF0000'); + expect(getColorFromHexRgbOrName('#CCC')).toBe('#CCC'); expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)'); expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)'); }); diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts index a99a93f4207..ee5741e794e 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts @@ -73,7 +73,7 @@ export const getColorDefinition = (hex: string, theme: GrafanaThemeType): ColorD }; const isHex = (color: string) => { - const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6})$/gi; + const hexRegex = /^((0x){0,1}|#{0,1})([0-9A-F]{8}|[0-9A-F]{6}|[0-9A-F]{3})$/gi; return hexRegex.test(color); }; From 748cb449117ac417b977a7ad1a4df2b08e205c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 8 Feb 2019 21:53:51 +0100 Subject: [PATCH 29/50] Fixed issues with plus button in threshold and panel option header, and current state in viz picker, fixes #15329 --- .../PanelOptionsGroup/_PanelOptionsGroup.scss | 4 ++-- .../ThresholdsEditor/_ThresholdsEditor.scss | 4 ++-- .../panel_editor/VisualizationTab.tsx | 20 ++++++++++++++----- public/sass/components/_panel_editor.scss | 12 ++++++----- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index b5b815cf57c..993bf086c95 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -30,13 +30,13 @@ &:hover { .panel-options-group__add-circle { background-color: $btn-success-bg; - color: $text-color-strong; + color: $white; } } } .panel-options-group__add-circle { - @include gradientBar($btn-success-bg, $btn-success-bg-hl, $text-color); + @include gradientBar($btn-success-bg, $btn-success-bg-hl); border-radius: 50px; width: 20px; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss index 200adfbfd75..490b452234f 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss +++ b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss @@ -21,7 +21,7 @@ } .thresholds-row-add-button { - @include buttonBackground($btn-success-bg, $btn-success-bg-hl, $text-color); + @include buttonBackground($btn-success-bg, $btn-success-bg-hl); align-self: center; margin-right: 5px; @@ -34,7 +34,7 @@ cursor: pointer; &:hover { - color: $text-color-strong; + color: $white; } } diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 94a403c11bf..0aeb8af41d9 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -119,7 +119,12 @@ export class VisualizationTab extends PureComponent { template += `
` + - (i > 0 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + (i > 0 + ? `
+ {{ctrl.editorTabs[${i}].title}} + +
` + : '') + `
@@ -228,8 +233,13 @@ export class VisualizationTab extends PureComponent { }; return ( - + <> { } const mapStateToProps = (state: StoreState) => ({ - urlOpenVizPicker: !!state.location.query.openVizPicker + urlOpenVizPicker: !!state.location.query.openVizPicker, }); const mapDispatchToProps = { - updateLocation + updateLocation, }; export default connectWithStore(VisualizationTab, mapStateToProps, mapDispatchToProps); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index b791231a242..1de136c09f1 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -146,15 +146,17 @@ padding-bottom: 6px; transition: transform 1 ease; - &--current { - box-shadow: 0 0 6px $orange; - border: 1px solid $orange; - } - &:hover { box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; + + } + + &--current { + box-shadow: 0 0 6px $orange !important; + border: 1px solid $orange !important; + background: $panel-editor-viz-item-bg !important; } } From bd6cefa53fc43e1a841ec13d448eefec77accb2a Mon Sep 17 00:00:00 2001 From: Nick Richards Date: Fri, 8 Feb 2019 14:51:50 -0800 Subject: [PATCH 30/50] Improve usability showing disabled lines in forms * Use gray-3 instead of gray-2 for text-color-weak in "light" theme --- public/sass/_variables.light.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 97d7a374765..0f4e15c91ec 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -56,7 +56,7 @@ $page-bg: $gray-7; $body-color: $gray-1; $text-color: $gray-1; $text-color-strong: $dark-2; -$text-color-weak: $gray-2; +$text-color-weak: $gray-3; $text-color-faint: $gray-4; $text-color-emphasis: $dark-5; From 2987a47a9b147bef3c8ebdc98ca962eac2a9cb4e Mon Sep 17 00:00:00 2001 From: Connor Patterson Date: Sat, 9 Feb 2019 13:47:08 -0500 Subject: [PATCH 31/50] Add aws ec2 api metrics for cloudwatch --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 34181d19673..8d186ba7a12 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -55,6 +55,7 @@ func init() { "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, "AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"}, "AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"}, + "AWS/EC2/API": {"ClientErrors","RequestLimitExceeded","ServerErrors","SuccessfulCalls"}, "AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"}, "AWS/ECS": {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"}, "AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"}, @@ -133,6 +134,7 @@ func init() { "AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation", "StreamLabel"}, "AWS/EBS": {"VolumeId"}, "AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, + "AWS/EC2/API": {}, "AWS/EC2Spot": {"AvailabilityZone", "FleetRequestId", "InstanceType"}, "AWS/ECS": {"ClusterName", "ServiceName"}, "AWS/EFS": {"FileSystemId"}, From d2aed7e075207fe5da0fa1ffbf7fa151ae1c029a Mon Sep 17 00:00:00 2001 From: Connor Patterson Date: Sat, 9 Feb 2019 14:13:15 -0500 Subject: [PATCH 32/50] Fix formatting --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 8d186ba7a12..ddda26dfd24 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -55,7 +55,7 @@ func init() { "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, "AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"}, "AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"}, - "AWS/EC2/API": {"ClientErrors","RequestLimitExceeded","ServerErrors","SuccessfulCalls"}, + "AWS/EC2/API": {"ClientErrors", "RequestLimitExceeded", "ServerErrors", "SuccessfulCalls"}, "AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"}, "AWS/ECS": {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"}, "AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"}, From 716db35faeb0942568968e879bd20d994a9454d3 Mon Sep 17 00:00:00 2001 From: thatsparesh <45209+thatsparesh@users.noreply.github.com> Date: Sat, 9 Feb 2019 14:56:43 -0600 Subject: [PATCH 33/50] remove unnecessary spy --- public/test/specs/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts index 1570c7dd9b7..b8307186540 100644 --- a/public/test/specs/helpers.ts +++ b/public/test/specs/helpers.ts @@ -143,7 +143,7 @@ export function DashboardViewStateStub(this: any) { } export function TimeSrvStub(this: any) { - this.init = sinon.spy(); + this.init = () => {}; this.time = { from: 'now-1h', to: 'now' }; this.timeRange = function(parse) { if (parse === false) { From 105879ab5dc59da377f794b4be582a4b90a7cd61 Mon Sep 17 00:00:00 2001 From: thatsparesh <45209+thatsparesh@users.noreply.github.com> Date: Sat, 9 Feb 2019 14:57:20 -0600 Subject: [PATCH 34/50] use timeSrv in metricFindQuery as timeRange --- .../plugins/datasource/mssql/datasource.ts | 14 ++---- .../datasource/mssql/specs/datasource.test.ts | 48 ++++++++++++++++++- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 1ede9cc3d1e..303cd0471d7 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -8,7 +8,7 @@ export class MssqlDatasource { interval: string; /** @ngInject */ - constructor(instanceSettings, private backendSrv, private $q, private templateSrv) { + constructor(instanceSettings, private backendSrv, private $q, private templateSrv, private timeSrv) { this.name = instanceSettings.name; this.id = instanceSettings.id; this.responseParser = new ResponseParser(this.$q); @@ -107,19 +107,13 @@ export class MssqlDatasource { format: 'table', }; + const range = this.timeSrv.timeRange(); const data = { queries: [interpolatedQuery], + from: range.from.valueOf().toString(), + to: range.to.valueOf().toString(), }; - if (optionalOptions && optionalOptions.range) { - if (optionalOptions.range.from) { - data['from'] = optionalOptions.range.from.valueOf().toString(); - } - if (optionalOptions.range.to) { - data['to'] = optionalOptions.range.to.valueOf().toString(); - } - } - return this.backendSrv .datasourceRequest({ url: '/api/tsdb/query', diff --git a/public/app/plugins/datasource/mssql/specs/datasource.test.ts b/public/app/plugins/datasource/mssql/specs/datasource.test.ts index 0dd496bfe59..a05848b3da8 100644 --- a/public/app/plugins/datasource/mssql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/specs/datasource.test.ts @@ -1,6 +1,6 @@ import moment from 'moment'; import { MssqlDatasource } from '../datasource'; -import { TemplateSrvStub } from 'test/specs/helpers'; +import { TemplateSrvStub, TimeSrvStub } from 'test/specs/helpers'; import { CustomVariable } from 'app/features/templating/custom_variable'; import q from 'q'; @@ -8,13 +8,14 @@ describe('MSSQLDatasource', () => { const ctx: any = { backendSrv: {}, templateSrv: new TemplateSrvStub(), + timeSrv: new TimeSrvStub(), }; beforeEach(() => { ctx.$q = q; ctx.instanceSettings = { name: 'mssql' }; - ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.$q, ctx.templateSrv); + ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.$q, ctx.templateSrv, ctx.timeSrv); }); describe('When performing annotationQuery', () => { @@ -188,6 +189,49 @@ describe('MSSQLDatasource', () => { }); }); + describe('When performing metricFindQuery', () => { + let results; + const query = 'select * from atable'; + const response = { + results: { + tempvar: { + meta: { + rowCount: 1, + }, + refId: 'tempvar', + tables: [ + { + columns: [{ text: 'title' }], + rows: [['aTitle']], + }, + ], + }, + }, + }; + const time = { + from: moment(1521545610656), + to: moment(1521546251185) + }; + + beforeEach(() => { + ctx.timeSrv.setTime(time); + + ctx.backendSrv.datasourceRequest = options => { + results = options.data; + return ctx.$q.when({ data: response, status: 200 }); + }; + + return ctx.ds.metricFindQuery(query); + }); + + it('should pass timerange to datasourceRequest', () => { + expect(results.from).toBe(time.from.valueOf().toString()); + expect(results.to).toBe(time.to.valueOf().toString()); + expect(results.queries.length).toBe(1); + expect(results.queries[0].rawSql).toBe(query); + }); + }); + describe('When interpolating variables', () => { beforeEach(() => { ctx.variable = new CustomVariable({}, {}); From a0729b9b50e4c7ad761fa2884c2a3b608c1c8b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 10 Feb 2019 17:05:58 +0100 Subject: [PATCH 35/50] provide time range to angular query controllers --- .../dashboard/panel_editor/QueryEditorRow.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index eda10087d41..83ef70f62e7 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -7,10 +7,11 @@ import _ from 'lodash'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import { Emitter } from 'app/core/utils/emitter'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; // Types import { PanelModel } from '../state/PanelModel'; -import { DataQuery, DataSourceApi } from '@grafana/ui'; +import { DataQuery, DataSourceApi, TimeRange } from '@grafana/ui'; interface Props { panel: PanelModel; @@ -43,8 +44,15 @@ export class QueryEditorRow extends PureComponent { componentDidMount() { this.loadDatasource(); + this.props.panel.events.on('refresh', this.onPanelRefresh); } + onPanelRefresh = () => { + if (this.state.angularScope) { + this.state.angularScope.range = getTimeSrv().timeRange(); + } + }; + getAngularQueryComponentScope(): AngularQueryComponentScope { const { panel, query } = this.props; const { datasource } = this.state; @@ -56,6 +64,7 @@ export class QueryEditorRow extends PureComponent { refresh: () => panel.refresh(), render: () => panel.render(), events: panel.events, + range: getTimeSrv().timeRange(), }; } @@ -97,6 +106,8 @@ export class QueryEditorRow extends PureComponent { } componentWillUnmount() { + this.props.panel.events.off('refresh', this.onPanelRefresh); + if (this.angularQueryEditor) { this.angularQueryEditor.destroy(); } @@ -250,4 +261,5 @@ export interface AngularQueryComponentScope { datasource: DataSourceApi; toggleEditorMode?: () => void; getCollapsedText?: () => string; + range: TimeRange; } From f38e64cc5da68bee5395004f5fb0cfd26cecaf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 10 Feb 2019 20:01:22 +0100 Subject: [PATCH 36/50] Navbar back button, no title edit this time --- .../dashboard/components/DashNav/DashNav.tsx | 30 ++++++------- .../dashboard/containers/DashboardPage.tsx | 3 +- public/sass/components/_navbar.scss | 44 ------------------- public/sass/components/_panel_editor.scss | 4 +- 4 files changed, 18 insertions(+), 63 deletions(-) diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 8560b3bfbba..6db07b5d42e 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -9,16 +9,16 @@ import { PlaylistSrv } from 'app/features/playlist/playlist_srv'; // Components import { DashNavButton } from './DashNavButton'; +import { Tooltip } from '@grafana/ui'; // State import { updateLocation } from 'app/core/actions'; // Types -import { DashboardModel, PanelModel } from '../../state'; +import { DashboardModel } from '../../state'; export interface Props { dashboard: DashboardModel; - fullscreenPanel?: PanelModel; editview: string; isEditing: boolean; isFullscreen: boolean; @@ -133,7 +133,7 @@ export class DashNav extends PureComponent { <>
- + {!this.isInFullscreenOrSettings && } {haveFolder && {folderTitle} / } {dashboard.title} @@ -144,24 +144,24 @@ export class DashNav extends PureComponent { ); } - renderPanelFullscreeMode() { - const { fullscreenPanel } = this.props; + get isInFullscreenOrSettings() { + return this.props.editview || this.props.isFullscreen; + } + renderBackButton() { return (
- -
- - -
+ + +
); } render() { - const { dashboard, onAddPanel, fullscreenPanel } = this.props; + const { dashboard, onAddPanel } = this.props; const { canStar, canSave, canShare, showSettings, isStarred } = dashboard.meta; const { snapshot } = dashboard; @@ -169,8 +169,8 @@ export class DashNav extends PureComponent { return (
- {!fullscreenPanel && this.renderDashboardTitleSearchButton()} - {fullscreenPanel && this.renderPanelFullscreeMode()} + {this.isInFullscreenOrSettings && this.renderBackButton()} + {this.renderDashboardTitleSearchButton()} {this.playlistSrv.isPlaying && (
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 724f3a625c0..27118e297b5 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -238,7 +238,7 @@ export class DashboardPage extends PureComponent { render() { const { dashboard, editview, $injector, isInitSlow, initError } = this.props; - const { isSettingsOpening, isEditing, isFullscreen, scrollTop, fullscreenPanel } = this.state; + const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state; if (!dashboard) { if (isInitSlow) { @@ -266,7 +266,6 @@ export class DashboardPage extends PureComponent { editview={editview} $injector={$injector} onAddPanel={this.onAddPanel} - fullscreenPanel={fullscreenPanel} />
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 0096810798a..ce0fb45051e 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -47,9 +47,6 @@ .navbar-button--add-panel, .navbar-button--star, .navbar-button--tv, - .navbar-page-btn .fa-caret-down { - display: none; - } .navbar-buttons--close { display: flex; @@ -185,7 +182,6 @@ height: $navbarHeight; align-items: center; padding-left: 7px; - flex-grow: 1; } .navbar-edit__back-btn { @@ -209,44 +205,4 @@ } } -.navbar-edit__input-wraper { - position: relative; - display: flex; - align-items: center; - flex-grow: 1; - &:hover { - i { - opacity: 1; - } - - .navbar-edit__input { - background: $input-bg; - flex-grow: 1; - @include form-control-focus(); - } - } - - i { - left: -25px; - position: relative; - color: $text-color-weak; - opacity: 0; - transition: 200ms opacity ease-in-out; - } -} - -.navbar-edit__input { - background: transparent; - transition: 200ms background ease-in-out; - width: auto; - font-size: $font-size-lg; - height: $gf-form-input-height; - padding: $input-padding-y $input-padding-x; - flex-grow: 1; - - &:focus { - @include form-control-focus(); - background: $input-bg; - } -} diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index e533681d672..b1d828069fb 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -87,8 +87,8 @@ margin: 0 $dashboard-padding; } - .panel-title-text { - visibility: hidden; + .search-container { + left: 0 !important; } } From 41217ea110875c703a50e047041464f0bb9bebbc Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:39:33 +0100 Subject: [PATCH 37/50] changelog: add notes about closing #13324 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a603fbb1a78..38d76762d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) +* **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) From 7b761f0a2864d0619817ca90942b91294f613354 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:44:04 +0100 Subject: [PATCH 38/50] changelog: add notes about closing #15189 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d76762d4b..79c52786898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) +### 6.6.0-beta1 fixes + +* **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) + # 6.0.0-beta1 (2019-01-30) ### New Features From 06972144d20b2125034e51db7c58573231d17e8a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:48:03 +0100 Subject: [PATCH 39/50] changelog: add notes about closing #14233 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c52786898..a5664a4205a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182) * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) +* **Cloudwatch**: Add AWS/EC2/API metrics [#14233](https://github.com/grafana/grafana/issues/14233), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) From be11da5b31892127d817cbc982a470f54602e813 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:49:10 +0100 Subject: [PATCH 40/50] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5664a4205a..b48766e97a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) -### 6.6.0-beta1 fixes +### 6.0.0-beta1 fixes * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) From 5eea85a3a3287b7916c7fb5e97db9d86bdba5add Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 09:06:20 +0100 Subject: [PATCH 41/50] changelog: add notes about closing #8570 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48766e97a3..30f4961343b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS/EC2/API metrics [#14233](https://github.com/grafana/grafana/issues/14233), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) +* **MySQL**: Adds datasource SSL CA/client certificates support [#8570](https://github.com/grafana/grafana/issues/8570), thx [@bugficks](https://github.com/bugficks) * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) From 9565e48f03535966bef099f4a19a3f6e418221c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 09:34:14 +0100 Subject: [PATCH 42/50] Fixed issue where double clicking on back button closes sidemenu --- public/app/core/components/sidemenu/SideMenu.test.tsx | 10 ++++++++++ public/app/core/components/sidemenu/SideMenu.tsx | 7 +++++++ public/app/core/reducers/location.ts | 2 ++ public/app/store/configureStore.ts | 4 ++-- public/app/types/location.ts | 1 + public/sass/components/_search.scss | 2 +- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/public/app/core/components/sidemenu/SideMenu.test.tsx b/public/app/core/components/sidemenu/SideMenu.test.tsx index 2a262adca5a..2286787d777 100644 --- a/public/app/core/components/sidemenu/SideMenu.test.tsx +++ b/public/app/core/components/sidemenu/SideMenu.test.tsx @@ -8,6 +8,16 @@ jest.mock('../../app_events', () => ({ emit: jest.fn(), })); +jest.mock('app/store/store', () => ({ + store: { + getState: jest.fn().mockReturnValue({ + location: { + lastUpdated: 0, + } + }) + } +})); + jest.mock('app/core/services/context_srv', () => ({ contextSrv: { sidemenu: true, diff --git a/public/app/core/components/sidemenu/SideMenu.tsx b/public/app/core/components/sidemenu/SideMenu.tsx index fd3e0d95564..29ef0fed069 100644 --- a/public/app/core/components/sidemenu/SideMenu.tsx +++ b/public/app/core/components/sidemenu/SideMenu.tsx @@ -3,9 +3,16 @@ import appEvents from '../../app_events'; import { contextSrv } from 'app/core/services/context_srv'; import TopSection from './TopSection'; import BottomSection from './BottomSection'; +import { store } from 'app/store/store'; export class SideMenu extends PureComponent { toggleSideMenu = () => { + // ignore if we just made a location change, stops hiding sidemenu on double clicks of back button + const timeSinceLocationChanged = new Date().getTime() - store.getState().location.lastUpdated; + if (timeSinceLocationChanged < 1000) { + return; + } + contextSrv.toggleSideMenu(); appEvents.emit('toggle-sidemenu'); }; diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts index c038ab53c9f..dff1ac8f5c1 100644 --- a/public/app/core/reducers/location.ts +++ b/public/app/core/reducers/location.ts @@ -9,6 +9,7 @@ export const initialState: LocationState = { query: {}, routeParams: {}, replace: false, + lastUpdated: 0, }; export const locationReducer = (state = initialState, action: Action): LocationState => { @@ -28,6 +29,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS query: { ...query }, routeParams: routeParams || state.routeParams, replace: replace === true, + lastUpdated: new Date().getTime(), }; } } diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index e2c33523271..2638587e96d 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; -import { createLogger } from 'redux-logger'; +// import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; import teamsReducers from 'app/features/teams/state/reducers'; @@ -41,7 +41,7 @@ export function configureStore() { if (process.env.NODE_ENV !== 'production') { // DEV builds we had the logger middleware - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } else { setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } diff --git a/public/app/types/location.ts b/public/app/types/location.ts index a47ef05d2be..4730f9d6ed7 100644 --- a/public/app/types/location.ts +++ b/public/app/types/location.ts @@ -15,6 +15,7 @@ export interface LocationState { query: UrlQueryMap; routeParams: UrlQueryMap; replace: boolean; + lastUpdated: number; } export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[]; diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index daad8fd10da..eba03283510 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -21,9 +21,9 @@ // Search .search-field-wrapper { width: 100%; + height: $navbarHeight; display: flex; background-color: $navbarBackground; - box-shadow: $navbarShadow; position: relative; & > input { From 58e57a1669e8c2f2d0f2036ffed68d61f5069de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 09:46:56 +0100 Subject: [PATCH 43/50] removed unused directive --- public/app/core/core.ts | 1 - public/app/core/directives/dash_class.ts | 39 ------------------------ 2 files changed, 40 deletions(-) delete mode 100644 public/app/core/directives/dash_class.ts diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 1f289fc4b27..80987b8fc88 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -1,4 +1,3 @@ -import './directives/dash_class'; import './directives/dropdown_typeahead'; import './directives/autofill_event_fix'; import './directives/metric_segment'; diff --git a/public/app/core/directives/dash_class.ts b/public/app/core/directives/dash_class.ts deleted file mode 100644 index 1fb93d29cf3..00000000000 --- a/public/app/core/directives/dash_class.ts +++ /dev/null @@ -1,39 +0,0 @@ -import $ from 'jquery'; -import _ from 'lodash'; -import coreModule from '../core_module'; - -/** @ngInject */ -function dashClass($timeout) { - return { - link: ($scope, elem) => { - const body = $('body'); - - $scope.ctrl.dashboard.events.on('view-mode-changed', panel => { - console.log('view-mode-changed', panel.fullscreen); - if (panel.fullscreen) { - body.addClass('panel-in-fullscreen'); - } else { - $timeout(() => { - body.removeClass('panel-in-fullscreen'); - }); - } - }); - - body.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true); - - $scope.$watch('ctrl.dashboardViewState.state.editview', newValue => { - if (newValue) { - elem.toggleClass('dashboard-page--settings-opening', _.isString(newValue)); - setTimeout(() => { - elem.toggleClass('dashboard-page--settings-open', _.isString(newValue)); - }, 10); - } else { - elem.removeClass('dashboard-page--settings-opening'); - elem.removeClass('dashboard-page--settings-open'); - } - }); - }, - }; -} - -coreModule.directive('dashClass', dashClass); From 3bf0a5ffc68ff395300e8191443d3bd999bcf97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:01:43 +0100 Subject: [PATCH 44/50] Fixed issue with logs graph not showing level names --- public/app/core/logs_model.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index abcd5563bd0..2cde5379448 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -340,6 +340,11 @@ export function makeSeriesForLogs(rows: LogRowModel[], intervalMs: number): Time return a[1] - b[1]; }); - return { datapoints: series.datapoints, target: series.alias, color: series.color }; + return { + datapoints: series.datapoints, + target: series.alias, + alias: series.alias, + color: series.color + }; }); } From e75e69a709c8fa4d4818f761f3c99edc21ede66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:27:04 +0100 Subject: [PATCH 45/50] Commented out the Loki dashboard query editor --- .../loki/components/LokiQueryEditor.tsx | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index a1b9e7a5df9..14fe046e098 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -2,61 +2,65 @@ import React, { PureComponent } from 'react'; // Components -import { Select, SelectOptionItem } from '@grafana/ui'; +// import { Select, SelectOptionItem } from '@grafana/ui'; // Types import { QueryEditorProps } from '@grafana/ui/src/types'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; -import { LokiQueryField } from './LokiQueryField'; +// import { LokiQueryField } from './LokiQueryField'; type Props = QueryEditorProps; -interface State { - query: LokiQuery; -} +// interface State { +// query: LokiQuery; +// } export class LokiQueryEditor extends PureComponent { - state: State = { - query: this.props.query, - }; - - onRunQuery = () => { - const { query } = this.state; - - this.props.onChange(query); - this.props.onRunQuery(); - }; - - onFieldChange = (query: LokiQuery, override?) => { - this.setState({ - query: { - ...this.state.query, - expr: query.expr, - }, - }); - }; - - onFormatChanged = (option: SelectOptionItem) => { - this.props.onChange({ - ...this.state.query, - resultFormat: option.value, - }); - }; + // state: State = { + // query: this.props.query, + // }; + // + // onRunQuery = () => { + // const { query } = this.state; + // + // this.props.onChange(query); + // this.props.onRunQuery(); + // }; + // + // onFieldChange = (query: LokiQuery, override?) => { + // this.setState({ + // query: { + // ...this.state.query, + // expr: query.expr, + // }, + // }); + // }; + // + // onFormatChanged = (option: SelectOptionItem) => { + // this.props.onChange({ + // ...this.state.query, + // resultFormat: option.value, + // }); + // }; render() { - const { query } = this.state; - const { datasource } = this.props; - const formatOptions: SelectOptionItem[] = [ - { label: 'Time Series', value: 'time_series' }, - { label: 'Table', value: 'table' }, - ]; - - query.resultFormat = query.resultFormat || 'time_series'; - const currentFormat = formatOptions.find(item => item.value === query.resultFormat); + // const { query } = this.state; + // const { datasource } = this.props; + // const formatOptions: SelectOptionItem[] = [ + // { label: 'Time Series', value: 'time_series' }, + // { label: 'Table', value: 'table' }, + // ]; + // + // query.resultFormat = query.resultFormat || 'time_series'; + // const currentFormat = formatOptions.find(item => item.value === query.resultFormat); return (
+
+
Loki is currently not supported as dashboard data source. We are working on it!
+
+ {/* {
+ */}
); } From 2c8c4729a8ad13a44b7fdef1c570cc940777fb10 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 10:47:03 +0100 Subject: [PATCH 46/50] changelog: adds note about closing #15288 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f4961343b..e6971f7952c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) +* **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) ### 6.0.0-beta1 fixes From 784d4fb70d676cf3f57fda39631e9c5bc451bdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:57:32 +0100 Subject: [PATCH 47/50] Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 658f1e34257..2cb8bfee306 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,18 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. +![](https://www.grafanacon.org/2019/images/grafanacon_la_nav-logo.png) + +Join us Feb 25-26 in Los Angeles, California for GrafanaCon - a two-day event with talks focused on Grafana and the surrounding open source monitoring ecosystem. Get deep dives into Loki, the Explore workflow and all of the new features of Grafana 6, plus participate in hands on workshops to help you get the most out of your data. + +Time is running out - grab your ticket now! http://grafanacon.org + + ## Installation -Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get) -the latest release. - -If you have any problems please read the [troubleshooting guide](http://docs.grafana.org/installation/troubleshooting/). +Head to [docs.grafana.org](http://docs.grafana.org/installation/) for documentation or [download](https://grafana.com/get) to get the latest release. ## Documentation & Support Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides. From f39fef2a027879d5c7385fc8b8e5515a938a6487 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 11:03:24 +0100 Subject: [PATCH 48/50] Clear visualization picker search on picker close --- package.json | 1 + public/app/core/components/Animations/FadeIn.tsx | 10 ++++++++-- .../dashboard/panel_editor/VisualizationTab.tsx | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5ac751ced3f..fae51a1d856 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/react-dom": "^16.0.9", "@types/react-grid-layout": "^0.16.6", "@types/react-select": "^2.0.4", + "@types/react-transition-group": "^2.0.15", "@types/react-virtualized": "^9.18.12", "angular-mocks": "1.6.6", "autoprefixer": "^6.4.0", diff --git a/public/app/core/components/Animations/FadeIn.tsx b/public/app/core/components/Animations/FadeIn.tsx index ea9a92d5f0f..d667b54261e 100644 --- a/public/app/core/components/Animations/FadeIn.tsx +++ b/public/app/core/components/Animations/FadeIn.tsx @@ -1,11 +1,12 @@ import React, { FC } from 'react'; -import Transition from 'react-transition-group/Transition'; +import Transition, { ExitHandler } from 'react-transition-group/Transition'; interface Props { duration: number; children: JSX.Element; in: boolean; unmountOnExit?: boolean; + onExited?: ExitHandler; } export const FadeIn: FC = props => { @@ -22,7 +23,12 @@ export const FadeIn: FC = props => { }; return ( - + {state => (
{ } } + clearQuery = () => { + this.setState({ searchQuery: '' }); + }; + onPanelOptionsChanged = (options: any) => { this.props.panel.updateOptions(options); this.forceUpdate(); @@ -241,7 +245,7 @@ export class VisualizationTab extends PureComponent { setScrollTop={this.setScrollTop} > <> - + Date: Mon, 11 Feb 2019 11:11:21 +0100 Subject: [PATCH 49/50] should be able to navigate to folder with only uid --- public/app/routes/routes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index e0029cf2464..4c9c5fd5304 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -150,8 +150,8 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { controllerAs: 'ctrl', }) .when('/dashboards/f/:uid', { - templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html', - controller: 'FolderDashboardsCtrl', + templateUrl: 'public/app/features/folders/partials/folder_dashboards.html', + controller: FolderDashboardsCtrl, controllerAs: 'ctrl', }) .when('/explore', { From b780b6377ae2cbc7f22b7245fb3c7b7145e7527a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 11:17:23 +0100 Subject: [PATCH 50/50] Fixed missing time axis on graph due to width not being passed --- public/app/features/explore/Explore.tsx | 1 + public/app/features/explore/Logs.tsx | 3 +++ public/app/features/explore/LogsContainer.tsx | 3 +++ 3 files changed, 7 insertions(+) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index a28776d813a..aca8f033fb3 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -220,6 +220,7 @@ export class Explore extends React.PureComponent { {supportsTable && } {supportsLogs && ( { range, scanning, scanRange, + width, } = this.props; if (!data) { @@ -215,6 +217,7 @@ export default class Logs extends PureComponent { { @@ -46,6 +47,7 @@ export class LogsContainer extends PureComponent { showingLogs, scanning, scanRange, + width, } = this.props; return ( @@ -63,6 +65,7 @@ export class LogsContainer extends PureComponent { range={range} scanning={scanning} scanRange={scanRange} + width={width} /> );