Merge branch 'master' into bar-gauge-poc

This commit is contained in:
Peter Holmberg 2019-02-19 15:43:08 +01:00
commit 5f56a07a23
41 changed files with 567 additions and 412 deletions

View File

@ -1,5 +1,14 @@
# 6.0.0-beta3 (unreleased)
### Minor
* **CLI**: Grafana CLI should preserve permissions for backend binaries for Linux and Darwin [#15500](https://github.com/grafana/grafana/issues/15500)
* **Alerting**: Allow image rendering 90 percent of alertTimeout [#15395](https://github.com/grafana/grafana/pull/15395)
### Bug fixes
* **Influxdb**: Add support for alerting on InfluxDB queries that use the non_negative_difference function [#15415](https://github.com/grafana/grafana/issues/15415), thx [@kiran3394](https://github.com/kiran3394)
* **Alerting**: Fix percent_diff calculation when points are nulls [#15443](https://github.com/grafana/grafana/issues/15443), thx [@max-neverov](https://github.com/max-neverov)
* **Alerting**: Fixed handling of alert urls with true flags [#15454](https://github.com/grafana/grafana/issues/15454)
# 6.0.0-beta2 (2019-02-11)
### New Features
@ -20,20 +29,19 @@
* **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288)
* **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131)
* **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432)
* **Provisioning**: Allow testing data sources that were added by config [#12164](https://github.com/grafana/grafana/issues/12164)
### Breaking changes
* **Internal Metrics** Edition has been added to the build_info metric. This will break any Graphite queries using this metric. Edition will be a new label for the Prometheus metric. [#15363](https://github.com/grafana/grafana/pull/15363)
### 6.0.0-beta1 fixes
### Bug fixes
* **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189)
* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295)
* **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219)
* **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122)
* **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222)
* **Explore**: Fix cut & paste adds newline before and after selection [#15223](https://github.com/grafana/grafana/issues/15223)
* **Dataproxy**: Fix global datasource proxy timeout not added to correct http client [#15258](https://github.com/grafana/grafana/issues/15258) [#5699](https://github.com/grafana/grafana/issues/5699)
* **Gauge**: Fix issue with gauge requests being cancelled [#15366](https://github.com/grafana/grafana/issues/15366)
* **Gauge**: Accept decimal inputs for thresholds [#15372](https://github.com/grafana/grafana/issues/15372)
* **UI**: Fix error caused by named colors that are not part of named colors palette [#15373](https://github.com/grafana/grafana/issues/15373)
* **Search**: Bug pressing special regexp chars in input fields [#12972](https://github.com/grafana/grafana/issues/12972)
* **Permissions**: No need to have edit permissions to be able to "Save as" [#13066](https://github.com/grafana/grafana/issues/13066)
# 6.0.0-beta1 (2019-01-30)
@ -82,6 +90,13 @@
* **Prometheus**: Query for annotation always uses 60s step regardless of dashboard range, fixes [#14795](https://github.com/grafana/grafana/issues/14795)
* **Annotations**: Fix creating annotation when graph panel has no data points position the popup outside viewport [#13765](https://github.com/grafana/grafana/issues/13765), thx [@banjeremy](https://github.com/banjeremy)
* **Piechart/Flot**: Fixes multiple piechart instances with donut bug [#15062](https://github.com/grafana/grafana/pull/15062)
* **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189)
* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295)
* **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219)
* **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122)
* **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222)
* **Explore**: Fix cut & paste adds newline before and after selection [#15223](https://github.com/grafana/grafana/issues/15223)
* **Dataproxy**: Fix global datasource proxy timeout not added to correct http client [#15258](https://github.com/grafana/grafana/issues/15258) [#5699](https://github.com/grafana/grafana/issues/5699)
### Breaking changes
* **Text Panel**: The text panel does no longer by default allow unsantizied HTML. [#4117](https://github.com/grafana/grafana/issues/4117). This means that if you have text panels with scripts tags they will no longer work as before. To enable unsafe javascript execution in text panels enable the settings `disable_sanitize_html` under the section `[panels]` in your Grafana ini file, or set env variable `GF_PANELS_DISABLE_SANITIZE_HTML=true`.

View File

@ -13,7 +13,6 @@ weight = 5
Grafana supports many different storage backends for your time series data (Data Source). Each Data Source has a specific Query Editor that is customized for the features and capabilities that the particular Data Source exposes.
## Querying
The query language and capabilities of each Data Source are obviously very different. You can combine data from multiple Data Sources onto a single Dashboard, but each Panel is tied to a specific Data Source that belongs to a particular Organization.
@ -28,6 +27,7 @@ The following datasources are officially supported:
* [InfluxDB]({{< relref "influxdb.md" >}})
* [OpenTSDB]({{< relref "opentsdb.md" >}})
* [Prometheus]({{< relref "prometheus.md" >}})
* [Loki]({{< relref "loki.md" >}})
* [MySQL]({{< relref "mysql.md" >}})
* [Postgres]({{< relref "postgres.md" >}})
* [Microsoft SQL Server (MSSQL)]({{< relref "mssql.md" >}})

View File

@ -0,0 +1,119 @@
+++
title = "Using Loki in Grafana"
description = "Guide for using Loki in Grafana"
keywords = ["grafana", "loki", "logging", "guide"]
type = "docs"
aliases = ["/datasources/loki"]
[menu.docs]
name = "Loki"
parent = "datasources"
weight = 11
+++
# Using Loki in Grafana
> BETA: Querying Loki data requires Grafana's Explore section.
> Grafana v6.x comes with Explore enabled by default.
> In Grafana v5.3.x and v5.4.x. you need to enable Explore manually.
> Viewing Loki data in dashboard panels is not supported yet, but is being worked on.
Grafana ships with built-in support for Loki, Grafana's log aggregation system.
Just add it as a datasource and you are ready to query your log data in [Explore](/features/explore).
## Adding the data source to Grafana
1. Open Grafana and make sure you are logged in.
2. In the side menu under the `Configuration` link you should find a link named `Data Sources`.
3. Click the `Add data source` button at the top.
4. Select `Loki` from the list of data sources.
> NOTE: If you're not seeing the `Data Sources` link in your side menu it means that your current user does not have the `Admin` role for the current organization.
| Name | Description |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| _Name_ | The datasource name. This is how you refer to the datasource in panels, queries, and Explore. |
| _Default_ | Default datasource means that it will be pre-selected for new panels. |
| _URL_ | The URL of the Loki instance, e.g., `http://localhost:3100` |
| _Maximum lines_ | Upper limit for number of log lines returned by Loki (default is 1000). Decrease if your browser is sluggish when displaying logs in Explore. |
## Querying Logs
Querying and displaying log data from Loki is available via [Explore](/features/explore).
Select the Loki data source, and then enter a log query to display your logs.
> Viewing Loki data in dashboard panels is not supported yet, but is being worked on.
### Log Queries
A log query consists of two parts: **log stream selector**, and a **search expression**. For performance reasons you need to start by choosing a log stream by selecting a log label.
The Logs Explorer (the `Log labels` button) next to the query field shows a list of labels of available log streams. An alternative way to write a query is to use the query field's autocomplete - you start by typing a left curly brace `{` and the autocomplete menu will suggest a list of labels. Press the `enter` key to execute the query.
Once the result is returned, the log panel shows a list of log rows and a bar chart where the x-axis shows the time and the y-axis shows the frequency/count.
<div class="medium-6 columns">
<video width="800" height="500" controls>
<source src="/assets/videos/explore_loki.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<br />
### Log Stream Selector
For the label part of the query expression, wrap it in curly braces `{}` and then use the key value syntax for selecting labels. Multiple label expressions are separated by a comma:
`{app="mysql",name="mysql-backup"}`
The following label matching operators are currently supported:
* `=` exactly equal.
* `!=` not equal.
* `=~` regex-match.
* `!~` do not regex-match.
Examples:
* `{name=~"mysql.+"}`
* `{name!~"mysql.+"}`
The [same rules that apply for Prometheus Label Selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#instant-vector-selectors) apply for Loki Log Stream Selectors.
Another way to add a label selector, is in the table section, clicking on the **Filter** button beside a label will add the label to the query expression. This even works for multiple queries and will the label selector to each query.
### Search Expression
After writing the Log Stream Selector, you can filter the results further by writing a search expression. The search expression can be just text or a regex expression.
Example queries:
* `{job="mysql"} error`
* `{name="kafka"} tsdb-ops.*io:2003`
* `{instance=~"kafka-[23]",name="kafka"} kafka.server:type=ReplicaManager`
## Templating
Template variables are not yet supported by Loki.
## Annotations
Annotations are not yet supported by Loki.
## Configure the Datasource with Provisioning
You can set up the datasource via config files with Grafana's provisioning system.
You can read more about how it works and all the settings you can set for datasources on the [provisioning docs page](/administration/provisioning/#datasources)
Here is an example:
```yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://localhost:3100
jsonData:
maxLines: 1000
```

View File

@ -67,9 +67,9 @@ The autocomplete menu can be trigger by pressing Ctrl + Space. The Autocomplete
Suggestions can appear under the query field - click on them to update your query with the suggested change.
- For counters (monotonously increasing metrics), a rate function will be suggested.
- For buckets, a histogram function will be suggested.
- For recording rules, possible to expand the rules.
* For counters (monotonously increasing metrics), a rate function will be suggested.
* For buckets, a histogram function will be suggested.
* For recording rules, possible to expand the rules.
### Table Filters
@ -79,6 +79,8 @@ Click on the filter button <span title="Filter for label" class="logs-label__ico
For Grafana 6.0, the first log integration is for the new open source log aggregation system from Grafana Labs - [Loki](https://github.com/grafana/loki). Loki is designed to be very cost effective, as it does not index the contents of the logs, but rather a set of labels for each log stream. The logs from Loki are queried in a similar way to querying with label selectors in Prometheus. It uses labels to group log streams which can be made to match up with your Prometheus labels. Read more about Grafana Loki [here](https://github.com/grafana/loki) or the Grafana Labs hosted variant: [Grafana Cloud Logs](https://grafana.com/loki).
See the [Loki's data source documentation](../datasources/loki) on how to query for log data.
### Switching from Metrics to Logs
If you switch from a Prometheus query to a logs query (you can do a split first to have your metrics and logs side by side) then it will keep the labels from your query that exist in the logs and use those to query the log streams. For example, the following Prometheus query:
@ -91,67 +93,18 @@ after switching to the Logs datasource, the query changes to:
This will return a chunk of logs in the selected time range that can be grepped/text searched.
### Log Queries
A log query consists of two parts: **log stream selector**, and a **search expression**. For performance reasons you need to start by choosing a log stream by selecting a log label.
The Logs Explorer (the `Log labels` button) next to the query field shows a list of labels of available log streams. An alternative way to write a query is to use the query field's autocomplete - you start by typing a left curly brace `{` and the autocomplete menu will suggest a list of labels. Press the `enter` key to execute the query.
Once the result is returned, the log panel shows a list of log rows and a bar chart where the x-axis shows the time and the y-axis shows the frequency/count.
<div class="medium-6 columns">
<video width="800" height="500" controls>
<source src="/assets/videos/explore_loki.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<br />
#### Log Stream Selector
For the label part of the query expression, wrap it in curly braces `{}` and then use the key value syntax for selecting labels. Multiple label expressions are separated by a comma:
`{app="mysql",name="mysql-backup"}`
The following label matching operators are currently supported:
- `=` exactly equal.
- `!=` not equal.
- `=~` regex-match.
- `!~` do not regex-match.
Examples:
- `{name=~"mysql.+"}`
- `{name!~"mysql.+"}`
The [same rules that apply for Prometheus Label Selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#instant-vector-selectors) apply for Loki Log Stream Selectors.
Another way to add a label selector, is in the table section, clicking on the **Filter** button beside a label will add the label to the query expression. This even works for multiple queries and will the label selector to each query.
#### Search Expression
After writing the Log Stream Selector, you can filter the results further by writing a search expression. The search expression can be just text or a regex expression.
Example queries:
- `{job="mysql"} error`
- `{name="kafka"} tsdb-ops.*io:2003`
- `{instance=~"kafka-[23]",name="kafka"} kafka.server:type=ReplicaManager`
### Deduping
Log data can be very repetitive and Explore can help by hiding duplicate log lines. There are a few different deduplication algorithms that you can use:
- `exact` Exact matches are done on the whole line, except for date fields.
- `numbers` Matches on the line after stripping out numbers (durations, IP addresses etc.).
- `signature` The most aggressive deduping - strips all letters and numbers, and matches on the remaining whitespace and punctuation.
* `exact` Exact matches are done on the whole line, except for date fields.
* `numbers` Matches on the line after stripping out numbers (durations, IP addresses etc.).
* `signature` The most aggressive deduping - strips all letters and numbers, and matches on the remaining whitespace and punctuation.
### Timestamp, Local time and Labels
There are some other check boxes under the logging graph apart from the Deduping options.
- Timestamp: shows/hides the Timestamp column
- Local time: shows/hides the Local time column
- Labels: shows/hides the label filters column
* Timestamp: shows/hides the Timestamp column
* Local time: shows/hides the Local time column
* Labels: shows/hides the label filters column

View File

@ -84,9 +84,9 @@ describe('Get thresholds formatted', () => {
it('should get the correct formatted values when thresholds are added', () => {
const { instance } = setup({
thresholds: [
{ index: 2, value: 75, color: '#6ED0E0' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
],
});

View File

@ -87,16 +87,15 @@ export class Gauge extends PureComponent<Props> {
getFormattedThresholds() {
const { maxValue, minValue, thresholds, theme } = this.props;
const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index);
const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1];
const lastThreshold = thresholds[thresholds.length - 1];
return [
...thresholdsSortedByIndex.map(threshold => {
...thresholds.map(threshold => {
if (threshold.index === 0) {
return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme.type) };
}
const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
const previousThreshold = thresholds[threshold.index - 1];
return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme.type) };
}),
{ value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme.type) },

View File

@ -17,7 +17,7 @@ export interface State {
export class Switch extends PureComponent<Props, State> {
state = {
id: _.uniqueId(),
id: _.uniqueId('check-'),
};
internalOnChange = (event: React.FormEvent<HTMLInputElement>) => {
@ -29,18 +29,20 @@ export class Switch extends PureComponent<Props, State> {
render() {
const { labelClass = '', switchClass = '', label, checked, transparent, className } = this.props;
const labelId = `check-${this.state.id}`;
const labelId = this.state.id;
const labelClassName = `gf-form-label ${labelClass} ${transparent ? 'gf-form-label--transparent' : ''} pointer`;
const switchClassName = `gf-form-switch ${switchClass} ${transparent ? 'gf-form-switch--transparent' : ''}`;
return (
<label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className}`}>
{label && <div className={labelClassName}>{label}</div>}
<div className={switchClassName}>
<input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
<span className="gf-form-switch__slider" />
</div>
</label>
<div className="gf-form-switch-container-react">
<label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className || ''}`}>
{label && <div className={labelClassName}>{label}</div>}
<div className={switchClassName}>
<input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
<span className="gf-form-switch__slider" />
</div>
</label>
</div>
);
}
}

View File

@ -1,9 +1,8 @@
import React, { ChangeEvent } from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import { ThresholdsEditor, Props } from './ThresholdsEditor';
const setup = (propOverrides?: object) => {
const setup = (propOverrides?: Partial<Props>) => {
const props: Props = {
onChange: jest.fn(),
thresholds: [],
@ -11,12 +10,26 @@ const setup = (propOverrides?: object) => {
Object.assign(props, propOverrides);
return shallow(<ThresholdsEditor {...props} />).instance() as ThresholdsEditor;
const wrapper = mount(<ThresholdsEditor {...props} />);
const instance = wrapper.instance() as ThresholdsEditor;
return {
instance,
wrapper,
};
};
describe('Render', () => {
it('should render with base threshold', () => {
const { wrapper } = setup();
expect(wrapper).toMatchSnapshot();
});
});
describe('Initialization', () => {
it('should add a base threshold if missing', () => {
const instance = setup();
const { instance } = setup();
expect(instance.state.thresholds).toEqual([{ index: 0, value: -Infinity, color: '#7EB26D' }]);
});
@ -24,7 +37,7 @@ describe('Initialization', () => {
describe('Add threshold', () => {
it('should not add threshold at index 0', () => {
const instance = setup();
const { instance } = setup();
instance.onAddThreshold(0);
@ -32,32 +45,32 @@ describe('Add threshold', () => {
});
it('should add threshold', () => {
const instance = setup();
const { instance } = setup();
instance.onAddThreshold(1);
expect(instance.state.thresholds).toEqual([
{ index: 1, value: 50, color: '#EAB839' },
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
]);
});
it('should add another threshold above a first', () => {
const instance = setup({
const { instance } = setup({
thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }, { index: 1, value: 50, color: '#EAB839' }],
});
instance.onAddThreshold(2);
expect(instance.state.thresholds).toEqual([
{ index: 2, value: 75, color: '#6ED0E0' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
]);
});
it('should add another threshold between first and second index', () => {
const instance = setup({
const { instance } = setup({
thresholds: [
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
@ -68,10 +81,10 @@ describe('Add threshold', () => {
instance.onAddThreshold(2);
expect(instance.state.thresholds).toEqual([
{ index: 3, value: 75, color: '#6ED0E0' },
{ index: 2, value: 62.5, color: '#EF843C' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 62.5, color: '#EF843C' },
{ index: 3, value: 75, color: '#6ED0E0' },
]);
});
});
@ -83,7 +96,7 @@ describe('Remove threshold', () => {
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
];
const instance = setup({ thresholds });
const { instance } = setup({ thresholds });
instance.onRemoveThreshold(thresholds[0]);
@ -96,9 +109,7 @@ describe('Remove threshold', () => {
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
];
const instance = setup({
thresholds,
});
const { instance } = setup({ thresholds });
instance.onRemoveThreshold(thresholds[1]);
@ -116,7 +127,7 @@ describe('change threshold value', () => {
{ index: 1, value: 50, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
];
const instance = setup({ thresholds });
const { instance } = setup({ thresholds });
const mockEvent = ({ target: { value: '12' } } as any) as ChangeEvent<HTMLInputElement>;
@ -126,7 +137,7 @@ describe('change threshold value', () => {
});
it('should update value', () => {
const instance = setup();
const { instance } = setup();
const thresholds = [
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 50, color: '#EAB839' },
@ -150,24 +161,24 @@ describe('change threshold value', () => {
});
describe('on blur threshold value', () => {
it('should resort rows and update indexes', () => {
const instance = setup();
it.only('should resort rows and update indexes', () => {
const { instance } = setup();
const thresholds = [
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 78, color: '#EAB839' },
{ index: 2, value: 75, color: '#6ED0E0' },
];
instance.state = {
instance.setState({
thresholds,
};
});
instance.onBlur();
expect(instance.state.thresholds).toEqual([
{ index: 2, value: 78, color: '#EAB839' },
{ index: 1, value: 75, color: '#6ED0E0' },
{ index: 0, value: -Infinity, color: '#7EB26D' },
{ index: 1, value: 75, color: '#6ED0E0' },
{ index: 2, value: 78, color: '#EAB839' },
]);
});
});

View File

@ -1,7 +1,7 @@
import React, { PureComponent, ChangeEvent } from 'react';
import { Threshold } from '../../types';
import { ColorPicker } from '../ColorPicker/ColorPicker';
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
import { ColorPicker } from '..';
import { PanelOptionsGroup } from '..';
import { colors } from '../../utils';
import { getColorFromHexRgbOrName, ThemeContext } from '@grafana/ui';
@ -54,16 +54,16 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
const value = afterThresholdValue - (afterThresholdValue - beforeThresholdValue) / 2;
// Set a color
const color = colors.filter(c => newThresholds.some(t => t.color === c) === false)[0];
const color = colors.filter(c => !newThresholds.some(t => t.color === c))[0];
this.setState(
{
thresholds: this.sortThresholds([
...newThresholds,
{
color,
index,
value: value as number,
color,
},
]),
},
@ -137,10 +137,11 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
onBlur = () => {
this.setState(prevState => {
const sortThresholds = this.sortThresholds([...prevState.thresholds]);
let index = sortThresholds.length - 1;
let index = 0;
sortThresholds.forEach(t => {
t.index = index--;
t.index = index++;
});
return { thresholds: sortThresholds };
});
@ -153,12 +154,11 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
sortThresholds = (thresholds: Threshold[]) => {
return thresholds.sort((t1, t2) => {
return t2.value - t1.value;
return t1.value - t2.value;
});
};
renderInput = (threshold: Threshold) => {
const value = threshold.index === 0 ? 'Base' : threshold.value;
return (
<div className="thresholds-row-input-inner">
<span className="thresholds-row-input-inner-arrow" />
@ -169,51 +169,60 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
</div>
)}
</div>
<div className="thresholds-row-input-inner-value">
<input
type="number"
step="0.0001"
onChange={event => this.onChangeThresholdValue(event, threshold)}
value={value}
onBlur={this.onBlur}
readOnly={threshold.index === 0}
/>
</div>
{threshold.index > 0 && (
<div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
<i className="fa fa-times" />
{threshold.index === 0 && (
<div className="thresholds-row-input-inner-value">
<input type="text" value="Base" readOnly />
</div>
)}
{threshold.index > 0 && (
<>
<div className="thresholds-row-input-inner-value">
<input
type="number"
step="0.0001"
onChange={event => this.onChangeThresholdValue(event, threshold)}
value={threshold.value}
onBlur={this.onBlur}
readOnly={threshold.index === 0}
/>
</div>
<div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
<i className="fa fa-times" />
</div>
</>
)}
</div>
);
};
render() {
const { thresholds } = this.state;
return (
<ThemeContext.Consumer>
{theme => {
return (
<PanelOptionsGroup title="Thresholds">
<div className="thresholds">
{thresholds.map((threshold, index) => {
return (
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
<div
className="thresholds-row-add-button"
onClick={() => this.onAddThreshold(threshold.index + 1)}
>
<i className="fa fa-plus" />
{thresholds
.slice(0)
.reverse()
.map((threshold, index) => {
return (
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
<div
className="thresholds-row-add-button"
onClick={() => this.onAddThreshold(threshold.index + 1)}
>
<i className="fa fa-plus" />
</div>
<div
className="thresholds-row-color-indicator"
style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
/>
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
</div>
<div
className="thresholds-row-color-indicator"
style={{ backgroundColor: getColorFromHexRgbOrName(threshold.color, theme.type) }}
/>
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
</div>
);
})}
);
})}
</div>
</PanelOptionsGroup>
);

View File

@ -43,7 +43,7 @@
}
.thresholds-row-input {
margin-top: 49px;
margin-top: 44px;
margin-left: 2px;
}

View File

@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render with base threshold 1`] = `
<ContextConsumer>
<Component />
</ContextConsumer>
`;

View File

@ -191,6 +191,7 @@ export const getCategories = (): ValueFormatCategory[] => [
{ name: 'Litre/hour', id: 'litreh', fn: toFixedUnit('l/h') },
{ name: 'Litre/min (l/min)', id: 'flowlpm', fn: toFixedUnit('l/min') },
{ name: 'milliLitre/min (mL/min)', id: 'flowmlpm', fn: toFixedUnit('mL/min') },
{ name: 'Lux (lx)', id: 'lux', fn: toFixedUnit('lux') },
],
},
{

View File

@ -145,7 +145,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
Text: "Explore",
Id: "explore",
SubTitle: "Explore your data",
Icon: "fa fa-rocket",
Icon: "gicon gicon-explore",
Url: setting.AppSubUrl + "/explore",
})
}

View File

@ -57,6 +57,8 @@ func installCommand(c CommandLine) error {
return InstallPlugin(pluginToInstall, version, c)
}
// InstallPlugin downloads the plugin code as a zip file from the Grafana.com API
// and then extracts the zip into the plugins directory.
func InstallPlugin(pluginName, version string, c CommandLine) error {
pluginFolder := c.PluginDirectory()
downloadURL := c.PluginURL()
@ -152,6 +154,10 @@ func downloadFile(pluginName, filePath, url string) (err error) {
return err
}
return extractFiles(body, pluginName, filePath)
}
func extractFiles(body []byte, pluginName string, filePath string) error {
r, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
return err
@ -161,12 +167,18 @@ func downloadFile(pluginName, filePath, url string) (err error) {
if zf.FileInfo().IsDir() {
err := os.Mkdir(newFile, 0777)
if PermissionsError(err) {
if permissionsError(err) {
return fmt.Errorf(permissionsDeniedMessage, newFile)
}
} else {
dst, err := os.Create(newFile)
if PermissionsError(err) {
fileMode := zf.Mode()
if strings.HasSuffix(newFile, "_linux_amd64") || strings.HasSuffix(newFile, "_darwin_amd64") {
fileMode = os.FileMode(0755)
}
dst, err := os.OpenFile(newFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
if permissionsError(err) {
return fmt.Errorf(permissionsDeniedMessage, newFile)
}
@ -184,6 +196,6 @@ func downloadFile(pluginName, filePath, url string) (err error) {
return nil
}
func PermissionsError(err error) bool {
func permissionsError(err error) bool {
return err != nil && strings.Contains(err.Error(), "permission denied")
}

View File

@ -1,6 +1,8 @@
package commands
import (
"io/ioutil"
"os"
"testing"
. "github.com/smartystreets/goconvey/convey"
@ -37,3 +39,42 @@ func TestFoldernameReplacement(t *testing.T) {
})
})
}
func TestExtractFiles(t *testing.T) {
Convey("Should preserve file permissions for plugin backend binaries for linux and darwin", t, func() {
err := os.RemoveAll("testdata/fake-plugins-dir")
So(err, ShouldBeNil)
err = os.MkdirAll("testdata/fake-plugins-dir", 0774)
So(err, ShouldBeNil)
body, err := ioutil.ReadFile("testdata/grafana-simple-json-datasource-ec18fa4da8096a952608a7e4c7782b4260b41bcf.zip")
So(err, ShouldBeNil)
err = extractFiles(body, "grafana-simple-json-datasource", "testdata/fake-plugins-dir")
So(err, ShouldBeNil)
//File in zip has permissions 777
fileInfo, err := os.Stat("testdata/fake-plugins-dir/grafana-simple-json-datasource/simple-plugin_darwin_amd64")
So(err, ShouldBeNil)
So(fileInfo.Mode().String(), ShouldEqual, "-rwxr-xr-x")
//File in zip has permission 664
fileInfo, err = os.Stat("testdata/fake-plugins-dir/grafana-simple-json-datasource/simple-plugin_linux_amd64")
So(err, ShouldBeNil)
So(fileInfo.Mode().String(), ShouldEqual, "-rwxr-xr-x")
//File in zip has permission 644
fileInfo, err = os.Stat("testdata/fake-plugins-dir/grafana-simple-json-datasource/simple-plugin_windows_amd64.exe")
So(err, ShouldBeNil)
So(fileInfo.Mode().String(), ShouldEqual, "-rw-r--r--")
//File in zip has permission 755
fileInfo, err = os.Stat("testdata/fake-plugins-dir/grafana-simple-json-datasource/non-plugin-binary")
So(err, ShouldBeNil)
So(fileInfo.Mode().String(), ShouldEqual, "-rwxr-xr-x")
err = os.RemoveAll("testdata/fake-plugins-dir")
So(err, ShouldBeNil)
})
}

View File

@ -52,6 +52,6 @@ func (srv *UserAuthTokenService) deleteExpiredTokens(maxInactiveLifetime, maxLif
return 0, nil
}
srv.log.Info("cleanup of expired auth tokens done", "count", affected)
srv.log.Debug("cleanup of expired auth tokens done", "count", affected)
return affected, err
}

View File

@ -65,7 +65,7 @@ export class DashNav extends PureComponent<Props> {
});
} else {
this.props.updateLocation({
query: { panelId: null, edit: null, fullscreen: null },
query: { panelId: null, edit: null, fullscreen: null, tab: null },
partial: true,
});
}

View File

@ -43,6 +43,7 @@ export class DashboardSrv {
delete urlParams.fullscreen;
delete urlParams.edit;
delete urlParams.panelId;
delete urlParams.tab;
this.$location.search(urlParams);
return;
}
@ -58,6 +59,7 @@ export class DashboardSrv {
urlParams.edit = true;
} else {
delete urlParams.edit;
delete urlParams.tab;
}
if (options.panelId || options.panelId === 0) {

View File

@ -399,6 +399,12 @@ export class DashboardMigrator {
prefix: panel.options.prefix,
suffix: panel.options.suffix,
};
// correct order
if (panel.options.thresholds) {
panel.options.thresholds.reverse();
}
// this options prop was due to a bug
delete panel.options.options;
delete panel.options.unit;

View File

@ -10,6 +10,20 @@ describe('PanelModel', () => {
type: 'table',
showColumns: true,
targets: [{ refId: 'A' }, { noRefId: true }],
options: {
thresholds: [
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: null,
},
],
},
});
});
@ -35,6 +49,21 @@ describe('PanelModel', () => {
expect(saveModel.events).toBe(undefined);
});
it('should restore -Infinity value for base threshold', () => {
expect(model.options.thresholds).toEqual([
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: -Infinity,
},
]);
});
describe('when changing panel type', () => {
beforeEach(() => {
model.changeType('graph', true);

View File

@ -3,7 +3,7 @@ import _ from 'lodash';
// Types
import { Emitter } from 'app/core/utils/emitter';
import { DataQuery, TimeSeries } from '@grafana/ui';
import { DataQuery, TimeSeries, Threshold } from '@grafana/ui';
import { TableData } from '@grafana/ui/src';
export interface GridPos {
@ -46,8 +46,6 @@ const mustKeepProps: { [str: string]: boolean } = {
timeFrom: true,
timeShift: true,
hideTimeOverride: true,
maxDataPoints: true,
interval: true,
description: true,
links: true,
fullscreen: true,
@ -91,7 +89,9 @@ export class PanelModel {
timeFrom?: any;
timeShift?: any;
hideTimeOverride?: any;
options: object;
options: {
[key: string]: any;
};
maxDataPoints?: number;
interval?: string;
@ -119,6 +119,8 @@ export class PanelModel {
_.defaultsDeep(this, _.cloneDeep(defaults));
// queries must have refId
this.ensureQueryIds();
this.restoreInfintyForThresholds();
}
ensureQueryIds() {
@ -131,6 +133,19 @@ export class PanelModel {
}
}
restoreInfintyForThresholds() {
if (this.options && this.options.thresholds) {
this.options.thresholds = this.options.thresholds.map((threshold: Threshold) => {
// JSON serialization of -Infinity is 'null' so lets convert it back to -Infinity
if (threshold.index === 0 && threshold.value === null) {
return { ...threshold, value: -Infinity };
}
return threshold;
});
}
}
getOptions(panelDefaults) {
return _.defaultsDeep(this.options || {}, panelDefaults);
}

View File

@ -103,18 +103,16 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
<div className="explore-toolbar-header-title">
{exploreId === 'left' && (
<span className="navbar-page-btn">
<i className="fa fa-rocket fa-fw" />
<i className="gicon gicon-explore" />
Explore
</span>
)}
</div>
<div className="explore-toolbar-header-close">
{exploreId === 'right' && (
<a onClick={this.props.closeSplit}>
<i className="fa fa-times fa-fw" />
</a>
)}
</div>
{exploreId === 'right' && (
<a className="explore-toolbar-header-close" onClick={this.props.closeSplit}>
<i className="fa fa-times fa-fw" />
</a>
)}
</div>
</div>
<div className="explore-toolbar-item">
@ -156,7 +154,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
splitted,
title: 'Run Query',
onClick: this.onRunQuery,
buttonClassName: 'navbar-button--primary',
buttonClassName: 'navbar-button--secondary',
iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon',
iconSide: IconSide.right,
})}

View File

@ -57,10 +57,17 @@ class GrafanaDatasource {
if (!_.isArray(options.annotation.tags) || options.annotation.tags.length === 0) {
return this.$q.when([]);
}
const delimiter = '__delimiter__';
const tags = [];
for (const t of params.tags) {
const renderedValues = this.templateSrv.replace(t, {}, 'pipe');
for (const tt of renderedValues.split('|')) {
const renderedValues = this.templateSrv.replace(t, {}, value => {
if (typeof value === 'string') {
return value;
}
return value.join(delimiter);
});
for (const tt of renderedValues.split(delimiter)) {
tags.push(tt);
}
}

View File

@ -14,7 +14,7 @@ describe('grafana data source', () => {
const templateSrvStub = {
replace: val => {
return val.replace('$var2', 'replaced|replaced2').replace('$var', 'replaced');
return val.replace('$var2', 'replaced__delimiter__replaced2').replace('$var', 'replaced');
},
};

View File

@ -1,4 +1,8 @@
import { GraphPanel } from './GraphPanel';
import { GraphPanelEditor } from './GraphPanelEditor';
import { ReactPanelPlugin } from '@grafana/ui';
export { GraphPanel as Panel, GraphPanelEditor as PanelOptions };
import { GraphPanelEditor } from './GraphPanelEditor';
import { GraphPanel } from './GraphPanel';
import { Options } from './types';
export const reactPanel = new ReactPanelPlugin<Options>(GraphPanel);
reactPanel.setEditor(GraphPanelEditor);

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="22px" height="22px" viewBox="0 0 22 22" style="enable-background:new 0 0 22 22;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#E3E3E3;}
</style>
<g>
<polygon class="st0" points="7.93,14.21 6.99,17.47 9.52,15.21 9,14.28 "/>
<polygon class="st0" points="14.15,7.86 15.08,4.6 12.56,6.86 13.08,7.79 "/>
<polygon class="st0" points="14.28,13.07 14.22,14.14 17.47,15.08 15.22,12.55 "/>
<polygon class="st0" points="7.8,9 7.86,7.93 4.61,6.99 6.86,9.52 "/>
<path class="st0" d="M8.82,1.31L8.36,9.35l-7.05,3.9l8.04,0.46l3.9,7.05l0.46-8.04l7.05-3.9l-8.04-0.46L8.82,1.31z M11.38,11.78
c-0.41,0.19-0.89,0.01-1.08-0.4c-0.19-0.41-0.01-0.89,0.4-1.08c0.41-0.19,0.89-0.01,1.08,0.4C11.97,11.1,11.79,11.59,11.38,11.78z"
/>
<path class="st1" d="M21.72,8.56c-1.35-5.92-7.24-9.63-13.16-8.28C2.64,1.63-1.07,7.52,0.28,13.44s7.24,9.63,13.16,8.28
C19.36,20.37,23.07,14.48,21.72,8.56z M15.08,4.6l-0.94,3.25l-1.07-0.06l-0.52-0.94L15.08,4.6z M7.86,7.93L7.8,9L6.86,9.52
L4.61,6.99L7.86,7.93z M6.99,17.47l0.94-3.25L9,14.28l0.52,0.94L6.99,17.47z M14.22,14.14l0.06-1.07l0.94-0.52l2.25,2.53
L14.22,14.14z M13.72,12.72l-0.46,8.04l-3.9-7.05l-8.04-0.46l7.05-3.9l0.46-8.04l3.9,7.05l8.04,0.46L13.72,12.72z"/>
<path class="st1" d="M10.7,10.29c-0.41,0.19-0.59,0.67-0.4,1.08c0.19,0.41,0.67,0.59,1.08,0.4c0.41-0.19,0.59-0.67,0.4-1.08
C11.59,10.28,11.1,10.11,10.7,10.29z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="22px" height="22px" viewBox="0 0 22 22" style="enable-background:new 0 0 22 22;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#52545c;}
</style>
<g>
<polygon class="st0" points="7.93,14.21 6.99,17.47 9.52,15.21 9,14.28 "/>
<polygon class="st0" points="14.15,7.86 15.08,4.6 12.56,6.86 13.08,7.79 "/>
<polygon class="st0" points="14.28,13.07 14.22,14.14 17.47,15.08 15.22,12.55 "/>
<polygon class="st0" points="7.8,9 7.86,7.93 4.61,6.99 6.86,9.52 "/>
<path class="st0" d="M8.82,1.31L8.36,9.35l-7.05,3.9l8.04,0.46l3.9,7.05l0.46-8.04l7.05-3.9l-8.04-0.46L8.82,1.31z M11.38,11.78
c-0.41,0.19-0.89,0.01-1.08-0.4c-0.19-0.41-0.01-0.89,0.4-1.08c0.41-0.19,0.89-0.01,1.08,0.4C11.97,11.1,11.79,11.59,11.38,11.78z"
/>
<path class="st1" d="M21.72,8.56c-1.35-5.92-7.24-9.63-13.16-8.28C2.64,1.63-1.07,7.52,0.28,13.44s7.24,9.63,13.16,8.28
C19.36,20.37,23.07,14.48,21.72,8.56z M15.08,4.6l-0.94,3.25l-1.07-0.06l-0.52-0.94L15.08,4.6z M7.86,7.93L7.8,9L6.86,9.52
L4.61,6.99L7.86,7.93z M6.99,17.47l0.94-3.25L9,14.28l0.52,0.94L6.99,17.47z M14.22,14.14l0.06-1.07l0.94-0.52l2.25,2.53
L14.22,14.14z M13.72,12.72l-0.46,8.04l-3.9-7.05l-8.04-0.46l7.05-3.9l0.46-8.04l3.9,7.05l8.04,0.46L13.72,12.72z"/>
<path class="st1" d="M10.7,10.29c-0.41,0.19-0.59,0.67-0.4,1.08c0.19,0.41,0.67,0.59,1.08,0.4c0.41-0.19,0.59-0.67,0.4-1.08
C11.59,10.28,11.1,10.11,10.7,10.29z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -60,7 +60,6 @@
@import 'components/panel_text';
@import 'components/panel_heatmap';
@import 'components/panel_logs';
@import 'components/settings_permissions';
@import 'components/tagsinput';
@import 'components/tables_lists';
@import 'components/search';
@ -110,7 +109,6 @@
@import 'pages/admin';
@import 'pages/alerting';
@import 'pages/history';
@import 'pages/plugins';
@import 'pages/signup';
@import 'pages/styleguide';
@import 'pages/errorpage';

View File

@ -12,28 +12,31 @@ $sapphire-shade: #1f60c4;
$lobster-base: #e02f44;
$lobster-shade: #c4162a;
$forest-light: #96d98d;
$forest-base: #37872d;
$forest-shade: #19730e;
$green-base: #299c46;
$green-shade: #23843b;
// Grays
// -------------------------
$black: #000;
$dark-1: #141414;
$dark-2: #1f1f20;
$dark-3: #262628;
$dark-4: #333333;
$dark-5: #444444;
$dark-2: #161719;
$dark-3: #1f1f20;
$dark-4: #212124;
$dark-5: #222426;
$dark-6: #262628;
$dark-7: #292a2d;
$dark-8: #2f2f32;
$dark-9: #343436;
$dark-10: #424345;
$gray-1: #555555;
$gray-2: #8e8e8e;
$gray-3: #b3b3b3;
$gray-4: #d8d9da;
$gray-5: #ececec;
$gray-7: #fbfbfb;
$gray-blue: #212327;
$input-black: #09090b;
$gray-blue: #212327;
$white: #fff;
@ -66,14 +69,14 @@ $critical: $lobster-base;
// Scaffolding
// -------------------------
$body-bg: rgb(23, 24, 25);
$page-bg: rgb(22, 23, 25);
$body-bg: $dark-2;
$page-bg: $dark-2;
$body-color: $gray-4;
$text-color: $gray-4;
$text-color-strong: $white;
$text-color-weak: $gray-2;
$text-color-faint: $dark-5;
$text-color-faint: $dark-10;
$text-color-emphasis: $gray-5;
$text-shadow-faint: 1px 1px 4px rgb(45, 45, 45);
@ -87,8 +90,8 @@ $brand-gradient: linear-gradient(
rgba(255, 68, 0, 0.7) 100%
);
$page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909);
$page-gradient: linear-gradient(180deg, $dark-5 10px, dark-2 100px);
$edit-gradient: linear-gradient(180deg, $dark-2 50%, $input-black);
// Links
// -------------------------
@ -100,60 +103,58 @@ $external-link-color: $sapphire-light;
// Typography
// -------------------------
$headings-color: darken($white, 11%);
$abbr-border-color: $gray-3 !default;
$abbr-border-color: $gray-2 !default;
$text-muted: $text-color-weak;
$hr-border-color: $dark-4;
$hr-border-color: $dark-9;
// Panel
// -------------------------
$panel-bg: #212124;
$panel-bg: $dark-4;
$panel-border: solid 1px $dark-1;
$panel-header-hover-bg: $dark-4;
$panel-header-hover-bg: $dark-9;
$panel-corner: $panel-bg;
// page header
$page-header-bg: linear-gradient(90deg, #292a2d, black);
$page-header-shadow: inset 0px -4px 14px $dark-2;
$page-header-border-color: $dark-4;
$page-header-bg: linear-gradient(90deg, $dark-7, $black);
$page-header-shadow: inset 0px -4px 14px $dark-3;
$page-header-border-color: $dark-9;
$divider-border-color: $gray-1;
// Graphite Target Editor
$tight-form-bg: $dark-3;
$tight-form-func-bg: $dark-4;
$tight-form-func-highlight-bg: $dark-5;
$tight-form-func-bg: $dark-9;
$tight-form-func-highlight-bg: $dark-10;
$modal-backdrop-bg: #353c42;
$code-tag-bg: $dark-1;
$code-tag-border: $dark-4;
$code-tag-border: $dark-9;
// cards
$card-background: linear-gradient(135deg, #2f2f32, #262628);
$card-background-hover: linear-gradient(135deg, #343436, #262628);
$card-background: linear-gradient(135deg, $dark-8, $dark-6);
$card-background-hover: linear-gradient(135deg, $dark-9, $dark-6);
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
// Lists
$list-item-bg: $card-background;
$list-item-hover-bg: lighten($gray-blue, 2%);
$list-item-hover-bg: $card-background-hover;
$list-item-link-color: $text-color;
$list-item-shadow: $card-shadow;
$empty-list-cta-bg: $gray-blue;
// Scrollbars
$scrollbarBackground: #404357;
$scrollbarBackground2: #3a3a3a;
$scrollbarBorder: black;
$scrollbarBackground: $dark-9;
$scrollbarBackground2: $dark-9;
$scrollbarBorder: $dark-10;
// Tables
// -------------------------
$table-bg: transparent; // overall background-color
$table-bg-accent: $dark-3; // for striping
$table-border: $dark-3; // table and cell border
$table-bg-accent: $dark-6; // for striping
$table-border: $dark-6; // table and cell border
$table-bg-odd: $dark-2;
$table-bg-hover: $dark-3;
$table-bg-odd: $dark-3;
$table-bg-hover: $dark-6;
// Buttons
// -------------------------
@ -170,8 +171,8 @@ $btn-success-bg-hl: $green-shade;
$btn-danger-bg: $lobster-base;
$btn-danger-bg-hl: $lobster-shade;
$btn-inverse-bg: $dark-3;
$btn-inverse-bg-hl: lighten($dark-3, 4%);
$btn-inverse-bg: $dark-6;
$btn-inverse-bg-hl: lighten($dark-6, 4%);
$btn-inverse-text-color: $link-color;
$btn-inverse-text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.1);
@ -179,24 +180,24 @@ $btn-link-color: $gray-3;
$iconContainerBackground: $black;
$btn-divider-left: $dark-4;
$btn-divider-right: $dark-2;
$btn-divider-left: $dark-9;
$btn-divider-right: $dark-3;
$btn-drag-image: '../img/grab_dark.svg';
// Forms
// -------------------------
$input-bg: $input-black;
$input-bg-disabled: $dark-3;
$input-bg-disabled: $dark-6;
$input-color: $gray-4;
$input-border-color: $dark-3;
$input-border-color: $dark-6;
$input-box-shadow: inset 1px 0px 0.3rem 0px rgba(150, 150, 150, 0.1);
$input-border-focus: $input-border-color;
$input-box-shadow-focus: rgba(102, 175, 233, 0.6);
$input-border-focus: $dark-6 !default;
$input-box-shadow-focus: $sapphire-light !default;
$input-color-placeholder: $gray-1 !default;
$input-label-bg: $gray-blue;
$input-label-border-color: $dark-3;
$input-label-border-color: $dark-6;
$input-color-select-arrow: $white;
// Input placeholder text color
@ -208,12 +209,12 @@ $search-filter-box-bg: $gray-blue;
// Typeahead
$typeahead-shadow: 0 5px 10px 0 $black;
$typeahead-selected-bg: $dark-4;
$typeahead-selected-bg: $dark-9;
$typeahead-selected-color: $yellow;
// Dropdowns
// -------------------------
$dropdownBackground: $dark-3;
$dropdownBackground: $dark-6;
$dropdownBorder: rgba(0, 0, 0, 0.2);
$dropdownDividerTop: transparent;
$dropdownDividerBottom: #444;
@ -222,7 +223,7 @@ $dropdownLinkColor: $text-color;
$dropdownLinkColorHover: $white;
$dropdownLinkColorActive: $white;
$dropdownLinkBackgroundHover: $dark-4;
$dropdownLinkBackgroundHover: $dark-9;
// Horizontal forms & lists
// -------------------------
@ -233,10 +234,7 @@ $horizontalComponentOffset: 180px;
$navbarHeight: 55px;
$navbarBackground: $panel-bg;
$navbarBorder: 1px solid $dark-3;
$navbarShadow: 0 0 20px black;
$navbarLinkColor: $gray-4;
$navbarBorder: 1px solid $dark-6;
$navbarButtonBackground: $navbarBackground;
$navbarButtonBackgroundHighlight: $body-bg;
@ -247,19 +245,19 @@ $navbar-button-border: #2f2f32;
// -------------------------
$side-menu-bg: $black;
$side-menu-bg-mobile: $side-menu-bg;
$side-menu-item-hover-bg: $dark-2;
$side-menu-item-hover-bg: $dark-3;
$side-menu-shadow: 0 0 20px black;
$side-menu-link-color: $link-color;
// Menu dropdowns
// -------------------------
$menu-dropdown-bg: $body-bg;
$menu-dropdown-hover-bg: $dark-2;
$menu-dropdown-hover-bg: $dark-3;
$menu-dropdown-shadow: 5px 5px 20px -5px $black;
// Tabs
// -------------------------
$tab-border-color: $dark-4;
$tab-border-color: $dark-9;
// Toolbar
$toolbar-bg: $input-black;
@ -276,9 +274,9 @@ $alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
$alert-info-bg: linear-gradient(100deg, $sapphire-base, $sapphire-shade);
// popover
$popover-bg: $page-bg;
$popover-bg: $dark-2;
$popover-color: $text-color;
$popover-border-color: $dark-4;
$popover-border-color: $dark-9;
$popover-shadow: 0 0 20px black;
$popover-help-bg: $btn-secondary-bg;
@ -324,13 +322,13 @@ $json-explorer-url-color: #027bff;
// Changelog and diff
// -------------------------
$diff-label-bg: $dark-2;
$diff-label-bg: $dark-3;
$diff-label-fg: $white;
$diff-group-bg: $dark-4;
$diff-group-bg: $dark-9;
$diff-arrow-color: $white;
$diff-json-bg: $dark-4;
$diff-json-bg: $dark-9;
$diff-json-fg: $gray-5;
$diff-json-added: $sapphire-shade;
@ -342,7 +340,7 @@ $diff-json-new: #457740;
$diff-json-changed-fg: $gray-5;
$diff-json-changed-num: $text-color;
$diff-json-icon: $gray-7;
$diff-json-icon: $gray-5;
//Submenu
$variable-option-bg: $dropdownLinkBackgroundHover;
@ -350,7 +348,7 @@ $variable-option-bg: $dropdownLinkBackgroundHover;
//Switch Slider
// -------------------------
$switch-bg: $input-bg;
$switch-slider-color: $dark-2;
$switch-slider-color: $dark-3;
$switch-slider-off-bg: $gray-1;
$switch-slider-on-bg: linear-gradient(90deg, #eb7b18, #d44a3a);
$switch-slider-shadow: 0 0 3px black;
@ -366,8 +364,8 @@ $checkbox-color: $dark-1;
// -------------------------
$panel-editor-shadow: 0 0 20px black;
$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
$panel-editor-viz-item-border: 1px solid $dark-5;
$panel-editor-viz-item-shadow: 0 0 8px $dark-10;
$panel-editor-viz-item-border: 1px solid $dark-10;
$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light;
$panel-editor-viz-item-border-hover: 1px solid $sapphire-light;
$panel-editor-viz-item-bg: $input-black;
@ -387,8 +385,8 @@ $logs-color-unkown: $gray-2;
// toggle-group
$button-toggle-group-btn-active-bg: linear-gradient(90deg, #eb7b18, #d44a3a);
$button-toggle-group-btn-active-shadow: inset 0 0 4px $black;
$button-toggle-group-btn-seperator-border: 1px solid $page-bg;
$button-toggle-group-btn-seperator-border: 1px solid $dark-2;
$vertical-resize-handle-bg: $dark-5;
$vertical-resize-handle-bg: $dark-10;
$vertical-resize-handle-dots: $gray-1;
$vertical-resize-handle-dots-hover: $gray-2;

View File

@ -6,13 +6,11 @@ $theme-name: light;
// New Colors
// -------------------------
$sapphire-faint: #f5f9ff;
$sapphire-light: #a8caff;
$sapphire-light: #5794f2;
$sapphire-base: #3274d9;
$sapphire-shade: #1f60c4;
$lobster-base: #e02f44;
$lobster-shade: #c4162a;
$green-base: #37872d;
$green-shade: #19730e;
$green-base: #3eb15b;
$green-shade: #369b4f;
$purple-shade: #8f3bb8;
@ -22,8 +20,6 @@ $yellow-base: #f2cc0c;
// -------------------------
$black: #000;
$dark-2: #1e2028;
$dark-3: #303133;
$dark-4: #35373f;
$dark-5: #41444b;
$gray-1: #52545c;
$gray-2: #767980;
@ -38,7 +34,6 @@ $white: #fff;
// Accent colors
// -------------------------
$blue: #0083b3;
$blue-light: #00a8e6;
$green: #3aa655;
$red: $lobster-base;
$yellow: #ff851b;
@ -96,7 +91,7 @@ $headings-color: $text-color;
$abbr-border-color: $gray-2 !default;
$text-muted: $text-color-weak;
$hr-border-color: $dark-3 !default;
$hr-border-color: $gray-4 !default;
// Panel
// -------------------------
@ -113,13 +108,12 @@ $page-header-border-color: $gray-4;
$divider-border-color: $gray-2;
// Graphite Target Editor
$tight-form-bg: #eaebee;
$tight-form-func-bg: $gray-5;
$tight-form-func-highlight-bg: $gray-6;
$modal-backdrop-bg: $body-bg;
$code-tag-bg: $gray-6;
$code-tag-border: darken($code-tag-bg, 3%);
$code-tag-border: $gray-4;
// cards
$card-background: linear-gradient(135deg, $gray-6, $gray-5);
@ -135,13 +129,12 @@ $list-item-shadow: $card-shadow;
$empty-list-cta-bg: $gray-6;
// Scrollbars
$scrollbarBackground: $gray-5;
$scrollbarBackground2: $gray-5;
$scrollbarBorder: $gray-4;
$scrollbarBackground: $gray-4;
$scrollbarBackground2: $gray-4;
$scrollbarBorder: $gray-3;
// Tables
// -------------------------
$table-bg: transparent; // overall background-color
$table-bg-accent: $gray-5; // for striping
$table-border: $gray-3; // table and cell border
@ -162,8 +155,9 @@ $btn-success-bg-hl: $green-shade;
$btn-danger-bg: $lobster-base;
$btn-danger-bg-hl: $lobster-shade;
$btn-inverse-bg: $gray-6;
$btn-inverse-bg-hl: darken($gray-6, 5%);
$btn-inverse-bg: $gray-5;
$btn-inverse-bg-hl: darken($gray-5, 5%);
$btn-inverse-bg-hl: $gray-4;
$btn-inverse-text-color: $gray-1;
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
@ -181,10 +175,10 @@ $btn-drag-image: '../img/grab_light.svg';
$input-bg: $white;
$input-bg-disabled: $gray-5;
$input-color: $dark-3;
$input-color: $dark-5;
$input-border-color: $gray-5;
$input-box-shadow: none;
$input-border-focus: $sapphire-light !default;
$input-border-focus: $gray-5 !default;
$input-box-shadow-focus: $sapphire-light !default;
$input-color-placeholder: $gray-4 !default;
$input-label-bg: $gray-5;
@ -210,7 +204,7 @@ $dropdownBorder: $gray-4;
$dropdownDividerTop: $gray-6;
$dropdownDividerBottom: $white;
$dropdownLinkColor: $dark-3;
$dropdownLinkColor: $dark-5;
$dropdownLinkColorHover: $link-color;
$dropdownLinkColorActive: $link-color;
@ -225,10 +219,7 @@ $horizontalComponentOffset: 180px;
$navbarHeight: 52px;
$navbarBackground: $white;
$navbarBorder: 1px solid $gray-4;
$navbarShadow: 0 0 3px #c1c1c1;
$navbarLinkColor: #444;
$navbarBorder: 1px solid $gray-5;
$navbarButtonBackground: lighten($navbarBackground, 3%);
$navbarButtonBackgroundHighlight: lighten($navbarBackground, 5%);
@ -265,7 +256,7 @@ $success-text-color: lighten($green, 10%);
$alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
$alert-success-bg: linear-gradient(90deg, $green-base, $green-shade);
$alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
$alert-info-bg: $sapphire-base;
$alert-info-bg: linear-gradient(100deg, $sapphire-base, $sapphire-shade);
// popover
$popover-bg: $page-bg;
@ -273,7 +264,7 @@ $popover-color: $text-color;
$popover-border-color: $gray-5;
$popover-shadow: 0 0 20px $white;
$popover-help-bg: $sapphire-base;
$popover-help-bg: $btn-secondary-bg;
$popover-help-color: $gray-6;
$popover-error-bg: $btn-danger-bg;
@ -316,13 +307,13 @@ $json-explorer-url-color: $sapphire-base;
// Changelog and diff
// -------------------------
$diff-label-bg: $gray-5;
$diff-label-bg: $gray-7;
$diff-label-fg: $gray-2;
$diff-arrow-color: $dark-3;
$diff-group-bg: $gray-7;
$diff-arrow-color: $dark-5;
$diff-group-bg: $gray-6;
$diff-json-bg: $gray-5;
$diff-json-bg: $gray-6;
$diff-json-fg: $gray-1;
$diff-json-added: $sapphire-shade;
@ -360,11 +351,13 @@ $panel-editor-shadow: 0px 0px 8px $gray-3;
$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
$panel-editor-viz-item-border: 1px solid $gray-3;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
$panel-editor-viz-item-border-hover: 1px solid $blue-light;
$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light;
$panel-editor-viz-item-border-hover: 1px solid $sapphire-light;
$panel-editor-viz-item-bg: $white;
$panel-editor-tabs-line-color: $dark-5;
$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
$panel-editor-viz-item-bg-hover: lighten($sapphire-base, 45%);
$panel-options-group-border: none;
$panel-options-group-header-bg: $gray-5;

View File

@ -192,6 +192,10 @@
background-image: url('../img/icons_#{$theme-name}_theme/icon_zoom_out.svg');
}
.gicon-explore {
background-image: url('../img/icons_#{$theme-name}_theme/icon_explore.svg');
}
.sidemenu {
.gicon-dashboard {
background-image: url('../img/icons_dark_theme/icon_dashboard.svg');
@ -205,6 +209,9 @@
.gicon-question {
background-image: url('../img/icons_dark_theme/icon_question.svg');
}
.gicon-explore {
background-image: url('../img/icons_dark_theme/icon_explore.svg');
}
}
.fa--permissions-list {

View File

@ -246,7 +246,7 @@ textarea {
table {
// Reset for nesting within parents with `background-color`.
background-color: $table-bg;
background-color: transparent;
}
caption {

View File

@ -266,7 +266,7 @@ dd {
// Abbreviations and acronyms
abbr[title] {
cursor: help;
border-bottom: 1px dotted $gray-2;
border-bottom: 1px dotted $abbr-border-color;
}
abbr.initialism {

View File

@ -1,9 +1,8 @@
.navbar {
position: relative;
padding-left: 20px;
z-index: $zindex-navbar-fixed;
height: $navbarHeight;
padding-right: 20px;
padding: 0 20px 0 50px;
display: flex;
flex-grow: 1;
border-bottom: 1px solid transparent;
@ -57,15 +56,14 @@
white-space: nowrap;
display: block;
margin: 0;
color: darken($link-color, 5%);
color: $headings-color;
font-size: $font-size-lg;
padding-left: 1rem;
min-height: $navbarHeight;
line-height: $navbarHeight;
.fa-caret-down {
font-size: 60%;
padding-left: 0.2rem;
padding-left: 6px;
}
&--search {
@ -73,12 +71,12 @@
}
.gicon {
top: -2px;
position: relative;
top: -1px;
font-size: 19px;
font-size: 17px;
line-height: 8px;
opacity: 0.75;
margin-right: 13px;
margin-right: 10px;
display: none;
}
@ -123,7 +121,7 @@
height: 30px;
color: $text-muted;
border: 1px solid $navbar-button-border;
margin-right: 3px;
margin-left: 3px;
white-space: nowrap;
.gicon {
@ -152,19 +150,19 @@
}
}
&--primary {
@include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
&--secondary {
@include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl);
}
}
@include media-breakpoint-up(sm) {
.navbar {
padding-left: 50px;
padding-left: 60px;
}
.sidemenu-open {
.navbar {
padding-left: 15px;
padding-left: 25px;
margin-left: 0;
}
}
@ -180,7 +178,7 @@
display: flex;
height: $navbarHeight;
align-items: center;
padding-left: 7px;
padding-right: 13px;
}
.navbar-edit__back-btn {

View File

@ -1,32 +0,0 @@
.permissionlist {
.permissionlist__section {
margin-bottom: $spacer*2;
}
.permissionlist__section-header {
margin-bottom: $spacer;
display: flex;
}
.permissionlist__section-header h6 {
margin: auto 5px;
color: $text-color-weak;
}
.permissionlist__section-header__add-button {
margin-left: auto;
width: 105px;
}
.permissionlist__item {
background-color: $tight-form-bg;
&:hover {
background-color: $tight-form-func-bg;
}
}
.permissionlist__item-buttons {
margin-left: auto;
}
}

View File

@ -253,7 +253,7 @@ li.sidemenu-org-switcher {
}
.sidemenu__logo_small_breakpoint {
padding: 16px 10px 26px;
padding: 14px 10px 26px 13px;
display: flex;
flex-direction: row;
justify-content: space-between;

View File

@ -13,6 +13,10 @@ gf-form-switch[disabled] {
}
}
.gf-form-switch-container-react {
display: flex;
}
.gf-form-switch-container {
display: flex;
cursor: pointer;

View File

@ -30,12 +30,6 @@
padding: 0;
margin-left: 0;
}
.explore-toolbar-header-title {
.navbar-page-btn {
padding-left: 0;
}
}
}
.explore-toolbar {
@ -44,7 +38,7 @@
flex-flow: row wrap;
justify-content: flex-start;
height: auto;
padding: 0px $dashboard-padding;
padding: 0px $dashboard-padding 0 25px;
border-bottom: 1px solid #0000;
transition-duration: 0.35s;
transition-timing-function: ease-in-out;
@ -87,22 +81,9 @@
align-items: center;
}
.explore-toolbar-header-title {
color: darken($link-color, 5%);
.navbar-page-btn {
padding-left: $dashboard-padding;
}
.fa {
font-size: 100%;
opacity: 0.75;
margin-right: 0.5em;
}
}
.explore-toolbar-header-close {
margin-left: auto;
color: $text-color-weak;
}
.explore-toolbar-content {
@ -156,7 +137,6 @@
.sidemenu-open {
.explore-toolbar-header-title {
.navbar-page-btn {
padding-left: 0;
margin-left: 0;
}
}
@ -164,7 +144,6 @@
.explore-toolbar-header-title {
.navbar-page-btn {
padding-left: 0;
margin-left: $dashboard-padding;
}
}
@ -185,7 +164,6 @@
.sidemenu-open {
.explore-toolbar-header-title {
.navbar-page-btn {
padding-left: 0;
margin-left: $dashboard-padding;
}
}
@ -193,7 +171,6 @@
.explore-toolbar-header-title {
.navbar-page-btn {
padding-left: 0;
margin-left: $dashboard-padding;
}
}

View File

@ -176,12 +176,6 @@ select:-webkit-autofill:focus {
}
}
.login-tab-header {
background: $tight-form-bg;
text-align: center;
margin-bottom: 3rem;
}
.login-change-password-info {
padding-bottom: 1.5rem;
@ -195,26 +189,6 @@ select:-webkit-autofill:focus {
background-color: $btn-semi-transparent;
}
.btn-login-tab {
background: transparent;
border: none;
font-size: 15px;
padding: 10px 10px;
font-weight: bold;
display: inline-block;
width: 170px;
color: $text-color;
&.active {
background: darken($tight-form-bg, 5%);
color: $white;
}
&:focus {
outline: 0;
}
}
.password-strength {
display: block;
width: 15%;
@ -252,15 +226,6 @@ select:-webkit-autofill:focus {
width: 100%;
}
.password-recovery {
background: $tight-form-bg;
padding: 10px;
a {
color: $gray-2;
}
}
.login-divider {
float: left;
width: 100%;

View File

@ -1,31 +0,0 @@
.get-more-plugins-link {
color: $gray-3;
font-size: $font-size-sm;
position: relative;
top: 1.2rem;
&:hover {
color: $link-hover-color;
}
img {
vertical-align: top;
}
}
@include media-breakpoint-down(sm) {
.get-more-plugins-link {
display: none;
}
}
.plugin-info-list-item {
white-space: nowrap;
max-width: $page-sidebar-width;
text-overflow: ellipsis;
overflow: hidden;
img {
width: 16px;
}
}