mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
commit
ea4223f923
@ -16,6 +16,7 @@
|
||||
* **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548)
|
||||
* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
|
||||
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
|
||||
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
|
||||
|
||||
### Bug fixes
|
||||
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
|
||||
|
@ -103,6 +103,9 @@ server_cert_name =
|
||||
# For "sqlite3" only, path relative to data_path setting
|
||||
path = grafana.db
|
||||
|
||||
# For "sqlite3" only. cache mode setting used for connecting to the database
|
||||
cache_mode = private
|
||||
|
||||
#################################### Session #############################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
|
||||
|
@ -99,6 +99,9 @@
|
||||
# Set to true to log the sql calls and execution times.
|
||||
log_queries =
|
||||
|
||||
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
|
||||
;cache_mode = private
|
||||
|
||||
#################################### Session ####################################
|
||||
[session]
|
||||
# Either "memory", "file", "redis", "mysql", "postgres", default is "file"
|
||||
|
7
devenv/docker/blocks/alert_webhook_listener/Dockerfile
Normal file
7
devenv/docker/blocks/alert_webhook_listener/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
FROM golang:latest
|
||||
ADD main.go /
|
||||
WORKDIR /
|
||||
RUN go build -o main .
|
||||
EXPOSE 3010
|
||||
ENTRYPOINT ["/main"]
|
@ -0,0 +1,5 @@
|
||||
alert_webhook_listener:
|
||||
build: docker/blocks/alert_webhook_listener
|
||||
network_mode: host
|
||||
ports:
|
||||
- "3010:3010"
|
24
devenv/docker/blocks/alert_webhook_listener/main.go
Normal file
24
devenv/docker/blocks/alert_webhook_listener/main.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
line := fmt.Sprintf("webbhook: -> %s", string(body))
|
||||
fmt.Println(line)
|
||||
io.WriteString(w, line)
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", hello)
|
||||
http.ListenAndServe(":3010", nil)
|
||||
}
|
@ -31,9 +31,10 @@ auto_sign_up = true
|
||||
ldap_sync_ttl = 60
|
||||
# Limit where auth proxy requests come from by configuring a list of IP addresses.
|
||||
# This can be used to prevent users spoofing the X-WEBAUTH-USER header.
|
||||
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
|
||||
whitelist =
|
||||
# Optionally define more headers to sync other user attributes
|
||||
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL``
|
||||
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL`
|
||||
headers =
|
||||
```
|
||||
|
||||
|
@ -250,6 +250,12 @@ Sets the maximum amount of time a connection may be reused. The default is 14400
|
||||
|
||||
Set to `true` to log the sql calls and execution times.
|
||||
|
||||
### cache_mode
|
||||
|
||||
For "sqlite3" only. [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database. (private, shared)
|
||||
Defaults to private.
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
## [security]
|
||||
|
@ -6,7 +6,9 @@ module.exports = {
|
||||
},
|
||||
"moduleDirectories": ["node_modules", "public"],
|
||||
"roots": [
|
||||
"<rootDir>/public"
|
||||
"<rootDir>/public/app",
|
||||
"<rootDir>/public/test",
|
||||
"<rootDir>/packages"
|
||||
],
|
||||
"testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": [
|
||||
|
57
package.json
57
package.json
@ -1,4 +1,5 @@
|
||||
{
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Torkel Ödegaard",
|
||||
"company": "Grafana Labs"
|
||||
@ -11,14 +12,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.1.0",
|
||||
"@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
|
||||
"@types/classnames": "^2.2.6",
|
||||
"@types/d3": "^4.10.1",
|
||||
"@types/enzyme": "^3.1.13",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/jquery": "^1.10.35",
|
||||
"@types/node": "^8.0.31",
|
||||
"@types/react": "^16.7.6",
|
||||
"@types/react-custom-scrollbars": "^4.0.5",
|
||||
@ -49,15 +52,12 @@
|
||||
"grunt-cli": "~1.2.0",
|
||||
"grunt-contrib-clean": "~1.0.0",
|
||||
"grunt-contrib-compress": "^1.3.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-cssmin": "~1.0.2",
|
||||
"grunt-exec": "^1.0.1",
|
||||
"grunt-newer": "^1.3.0",
|
||||
"grunt-notify": "^0.4.5",
|
||||
"grunt-postcss": "^0.8.0",
|
||||
"grunt-sass": "^2.0.0",
|
||||
"grunt-sass-lint": "^0.2.2",
|
||||
"grunt-sass-lint": "^0.2.4",
|
||||
"grunt-usemin": "3.1.1",
|
||||
"grunt-webpack": "^3.0.2",
|
||||
"html-loader": "^0.5.1",
|
||||
@ -73,6 +73,7 @@
|
||||
"ng-annotate-webpack-plugin": "^0.3.0",
|
||||
"ngtemplate-loader": "^2.0.1",
|
||||
"npm": "^5.4.2",
|
||||
"node-sass": "^4.11.0",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||
"phantomjs-prebuilt": "^2.1.15",
|
||||
"postcss-browser-reporter": "^0.5.0",
|
||||
@ -92,6 +93,7 @@
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-loader": "^3.5.3",
|
||||
"tslint-react": "^3.6.0",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"webpack": "4.19.1",
|
||||
@ -108,15 +110,30 @@
|
||||
"watch": "webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js",
|
||||
"build": "grunt build",
|
||||
"test": "grunt test",
|
||||
"lint": "tslint -c tslint.json --project tsconfig.json",
|
||||
"tslint": "tslint -c tslint.json --project tsconfig.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"jest": "jest --notify --watch",
|
||||
"api-tests": "jest --notify --watch --config=tests/api/jest.js",
|
||||
"precommit": "lint-staged && grunt precommit"
|
||||
"precommit": "grunt precommit"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged && grunt precommit"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": ["prettier --write", "git add"],
|
||||
"*.scss": ["prettier --write", "git add"],
|
||||
"*pkg/**/*.go": ["gofmt -w -s", "git add"]
|
||||
"*.{ts,tsx}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
"*.scss": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
"*pkg/**/*.go": [
|
||||
"gofmt -w -s",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
@ -126,6 +143,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"@torkelo/react-select": "2.1.1",
|
||||
"angular": "1.6.6",
|
||||
"angular-bindonce": "0.3.1",
|
||||
"angular-native-dragdrop": "1.2.2",
|
||||
@ -133,7 +151,7 @@
|
||||
"angular-sanitize": "1.6.6",
|
||||
"baron": "^3.0.3",
|
||||
"brace": "^0.10.0",
|
||||
"classnames": "^2.2.5",
|
||||
"classnames": "^2.2.6",
|
||||
"clipboard": "^1.7.1",
|
||||
"d3": "^4.11.0",
|
||||
"d3-scale-chromatic": "^1.3.0",
|
||||
@ -152,10 +170,9 @@
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-grid-layout": "0.16.6",
|
||||
"react-popper": "^1.3.0",
|
||||
"react-highlight-words": "0.11.0",
|
||||
"react-popper": "^1.3.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"@torkelo/react-select": "2.1.1",
|
||||
"react-sizeme": "^2.3.6",
|
||||
"react-table": "^6.8.6",
|
||||
"react-transition-group": "^2.2.1",
|
||||
@ -165,18 +182,26 @@
|
||||
"redux-thunk": "^2.3.0",
|
||||
"remarkable": "^1.7.1",
|
||||
"rst2html": "github:thoward/rst2html#990cb89",
|
||||
"rxjs": "^5.4.3",
|
||||
"rxjs": "^6.3.3",
|
||||
"slate": "^0.33.4",
|
||||
"slate-plain-serializer": "^0.5.10",
|
||||
"slate-prism": "^0.5.0",
|
||||
"slate-react": "^0.12.4",
|
||||
"tether": "^1.4.0",
|
||||
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||
"tinycolor2": "^1.4.1",
|
||||
"tslint-react": "^3.6.0"
|
||||
"tinycolor2": "^1.4.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"caniuse-db": "1.0.30000772",
|
||||
"**/@types/react": "16.7.6"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@types/*",
|
||||
"**/@types/*/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
4
packages/grafana-build/README.md
Normal file
4
packages/grafana-build/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Shared build scripts
|
||||
|
||||
Shared build scripts for plugins & internal packages.
|
||||
|
13
packages/grafana-build/package.json
Normal file
13
packages/grafana-build/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@grafana/build",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"tslint": "echo \"Nothing to do\"",
|
||||
"typecheck": "echo \"Nothing to do\""
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
3
packages/grafana-ui/README.md
Normal file
3
packages/grafana-ui/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Grafana (WIP) shared component library
|
||||
|
||||
Used by internal & external plugins.
|
33
packages/grafana-ui/package.json
Normal file
33
packages/grafana-ui/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@grafana/ui",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"tslint": "tslint -c tslint.json --project tsconfig.json",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@torkelo/react-select": "2.1.1",
|
||||
"classnames": "^2.2.5",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.10",
|
||||
"moment": "^2.22.2",
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-highlight-words": "0.11.0",
|
||||
"react-popper": "^1.3.0",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"react-virtualized": "^9.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/lodash": "^4.14.119",
|
||||
"@types/react": "^16.7.6",
|
||||
"@types/classnames": "^2.2.6",
|
||||
"@types/jquery": "^1.10.35",
|
||||
"typescript": "^3.2.2"
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { DeleteButton } from './DeleteButton';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
describe('DeleteButton', () => {
|
||||
let wrapper;
|
||||
let deleted;
|
||||
let wrapper: any;
|
||||
let deleted: any;
|
||||
|
||||
beforeAll(() => {
|
||||
deleted = false;
|
||||
@ -12,7 +12,8 @@ describe('DeleteButton', () => {
|
||||
function deleteItem() {
|
||||
deleted = true;
|
||||
}
|
||||
wrapper = shallow(<DeleteButton onConfirmDelete={() => deleteItem()} />);
|
||||
|
||||
wrapper = shallow(<DeleteButton onConfirm={() => deleteItem()} />);
|
||||
});
|
||||
|
||||
it('should show confirm delete when clicked', () => {
|
@ -1,19 +1,19 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent, SyntheticEvent } from 'react';
|
||||
|
||||
export interface DeleteButtonProps {
|
||||
onConfirmDelete();
|
||||
interface Props {
|
||||
onConfirm(): void;
|
||||
}
|
||||
|
||||
export interface DeleteButtonStates {
|
||||
interface State {
|
||||
showConfirm: boolean;
|
||||
}
|
||||
|
||||
export default class DeleteButton extends PureComponent<DeleteButtonProps, DeleteButtonStates> {
|
||||
state: DeleteButtonStates = {
|
||||
export class DeleteButton extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
showConfirm: false,
|
||||
};
|
||||
|
||||
onClickDelete = event => {
|
||||
onClickDelete = (event: SyntheticEvent) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -23,7 +23,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
|
||||
});
|
||||
};
|
||||
|
||||
onClickCancel = event => {
|
||||
onClickCancel = (event: SyntheticEvent) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -33,7 +33,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
|
||||
};
|
||||
|
||||
render() {
|
||||
const onClickConfirm = this.props.onConfirmDelete;
|
||||
const { onConfirm } = this.props;
|
||||
let showConfirm;
|
||||
let showDeleteButton;
|
||||
|
||||
@ -55,7 +55,7 @@ export default class DeleteButton extends PureComponent<DeleteButtonProps, Delet
|
||||
<a className="btn btn-small" onClick={this.onClickCancel}>
|
||||
Cancel
|
||||
</a>
|
||||
<a className="btn btn-danger btn-small" onClick={onClickConfirm}>
|
||||
<a className="btn btn-danger btn-small" onClick={onConfirm}>
|
||||
Confirm Delete
|
||||
</a>
|
||||
</span>
|
1
packages/grafana-ui/src/components/index.scss
Normal file
1
packages/grafana-ui/src/components/index.scss
Normal file
@ -0,0 +1 @@
|
||||
@import 'DeleteButton/DeleteButton';
|
1
packages/grafana-ui/src/components/index.ts
Normal file
1
packages/grafana-ui/src/components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { DeleteButton } from './DeleteButton/DeleteButton';
|
23
packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx
Normal file
23
packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React, { SFC, ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
htmlFor?: string;
|
||||
className?: string;
|
||||
isFocused?: boolean;
|
||||
isInvalid?: boolean;
|
||||
}
|
||||
|
||||
export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
|
||||
const classes = classNames('gf-form-label', className, {
|
||||
'gf-form-label--is-focused': isFocused,
|
||||
'gf-form-label--is-invalid': isInvalid,
|
||||
});
|
||||
|
||||
return (
|
||||
<label className={classes} {...rest} htmlFor={htmlFor}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
};
|
1
packages/grafana-ui/src/forms/index.ts
Normal file
1
packages/grafana-ui/src/forms/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
|
1
packages/grafana-ui/src/index.scss
Normal file
1
packages/grafana-ui/src/index.scss
Normal file
@ -0,0 +1 @@
|
||||
@import 'components/index';
|
5
packages/grafana-ui/src/index.ts
Normal file
5
packages/grafana-ui/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './components';
|
||||
export * from './visualizations';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
export * from './forms';
|
3
packages/grafana-ui/src/types/index.ts
Normal file
3
packages/grafana-ui/src/types/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './series';
|
||||
export * from './time';
|
||||
export * from './panel';
|
17
packages/grafana-ui/src/types/jquery.d.ts
vendored
Normal file
17
packages/grafana-ui/src/types/jquery.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
interface JQueryPlot {
|
||||
(element: HTMLElement | JQuery, data: any, options: any): void;
|
||||
plugins: any[];
|
||||
}
|
||||
|
||||
interface JQueryStatic {
|
||||
plot: JQueryPlot;
|
||||
}
|
||||
|
||||
interface JQuery {
|
||||
place_tt: any;
|
||||
modal: any;
|
||||
tagsinput: any;
|
||||
typeahead: any;
|
||||
accessKey: any;
|
||||
tooltip: any;
|
||||
}
|
31
packages/grafana-ui/src/types/panel.ts
Normal file
31
packages/grafana-ui/src/types/panel.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { TimeSeries, LoadingState } from './series';
|
||||
import { TimeRange } from './time';
|
||||
|
||||
export interface PanelProps<T = any> {
|
||||
timeSeries: TimeSeries[];
|
||||
timeRange: TimeRange;
|
||||
loading: LoadingState;
|
||||
options: T;
|
||||
renderCounter: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface PanelOptionsProps<T = any> {
|
||||
options: T;
|
||||
onChange: (options: T) => void;
|
||||
}
|
||||
|
||||
export interface PanelSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface PanelMenuItem {
|
||||
type?: 'submenu' | 'divider';
|
||||
text?: string;
|
||||
iconClassName?: string;
|
||||
onClick?: () => void;
|
||||
shortcut?: string;
|
||||
subMenu?: PanelMenuItem[];
|
||||
}
|
53
packages/grafana-ui/src/types/series.ts
Normal file
53
packages/grafana-ui/src/types/series.ts
Normal file
@ -0,0 +1,53 @@
|
||||
export enum LoadingState {
|
||||
NotStarted = 'NotStarted',
|
||||
Loading = 'Loading',
|
||||
Done = 'Done',
|
||||
Error = 'Error',
|
||||
}
|
||||
|
||||
export type TimeSeriesValue = number | null;
|
||||
|
||||
export type TimeSeriesPoints = TimeSeriesValue[][];
|
||||
|
||||
export interface TimeSeries {
|
||||
target: string;
|
||||
datapoints: TimeSeriesPoints;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
/** View model projection of a time series */
|
||||
export interface TimeSeriesVM {
|
||||
label: string;
|
||||
color: string;
|
||||
data: TimeSeriesValue[][];
|
||||
stats: TimeSeriesStats;
|
||||
}
|
||||
|
||||
export interface TimeSeriesStats {
|
||||
total: number | null;
|
||||
max: number | null;
|
||||
min: number | null;
|
||||
logmin: number;
|
||||
avg: number | null;
|
||||
current: number | null;
|
||||
first: number | null;
|
||||
delta: number;
|
||||
diff: number | null;
|
||||
range: number | null;
|
||||
timeStep: number;
|
||||
count: number;
|
||||
allIsNull: boolean;
|
||||
allIsZero: boolean;
|
||||
}
|
||||
|
||||
export enum NullValueMode {
|
||||
Null = 'null',
|
||||
Ignore = 'connected',
|
||||
AsZero = 'null as zero',
|
||||
}
|
||||
|
||||
/** View model projection of many time series */
|
||||
export interface TimeSeriesVMs {
|
||||
[index: number]: TimeSeriesVM;
|
||||
length: number;
|
||||
}
|
17
packages/grafana-ui/src/types/time.ts
Normal file
17
packages/grafana-ui/src/types/time.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Moment } from 'moment';
|
||||
|
||||
export interface RawTimeRange {
|
||||
from: Moment | string;
|
||||
to: Moment | string;
|
||||
}
|
||||
|
||||
export interface TimeRange {
|
||||
from: Moment;
|
||||
to: Moment;
|
||||
raw: RawTimeRange;
|
||||
}
|
||||
|
||||
export interface IntervalValues {
|
||||
interval: string; // 10s,5m
|
||||
intervalMs: number;
|
||||
}
|
1
packages/grafana-ui/src/utils/index.ts
Normal file
1
packages/grafana-ui/src/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './processTimeSeries';
|
174
packages/grafana-ui/src/utils/processTimeSeries.ts
Normal file
174
packages/grafana-ui/src/utils/processTimeSeries.ts
Normal file
@ -0,0 +1,174 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
|
||||
// Types
|
||||
import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types';
|
||||
|
||||
interface Options {
|
||||
timeSeries: TimeSeries[];
|
||||
nullValueMode: NullValueMode;
|
||||
colorPalette: string[];
|
||||
}
|
||||
|
||||
export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs {
|
||||
const vmSeries = timeSeries.map((item, index) => {
|
||||
const colorIndex = index % colorPalette.length;
|
||||
const label = item.target;
|
||||
const result = [];
|
||||
|
||||
// stat defaults
|
||||
let total = 0;
|
||||
let max: TimeSeriesValue = -Number.MAX_VALUE;
|
||||
let min: TimeSeriesValue = Number.MAX_VALUE;
|
||||
let logmin = Number.MAX_VALUE;
|
||||
let avg: TimeSeriesValue = null;
|
||||
let current: TimeSeriesValue = null;
|
||||
let first: TimeSeriesValue = null;
|
||||
let delta: TimeSeriesValue = 0;
|
||||
let diff: TimeSeriesValue = null;
|
||||
let range: TimeSeriesValue = null;
|
||||
let timeStep = Number.MAX_VALUE;
|
||||
let allIsNull = true;
|
||||
let allIsZero = true;
|
||||
|
||||
const ignoreNulls = nullValueMode === NullValueMode.Ignore;
|
||||
const nullAsZero = nullValueMode === NullValueMode.AsZero;
|
||||
|
||||
let currentTime: TimeSeriesValue = null;
|
||||
let currentValue: TimeSeriesValue = null;
|
||||
let nonNulls = 0;
|
||||
let previousTime: TimeSeriesValue = null;
|
||||
let previousValue = 0;
|
||||
let previousDeltaUp = true;
|
||||
|
||||
for (let i = 0; i < item.datapoints.length; i++) {
|
||||
currentValue = item.datapoints[i][0];
|
||||
currentTime = item.datapoints[i][1];
|
||||
|
||||
if (typeof currentTime !== 'number') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof currentValue !== 'number') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Due to missing values we could have different timeStep all along the series
|
||||
// so we have to find the minimum one (could occur with aggregators such as ZimSum)
|
||||
if (previousTime !== null && currentTime !== null) {
|
||||
const currentStep = currentTime - previousTime;
|
||||
if (currentStep < timeStep) {
|
||||
timeStep = currentStep;
|
||||
}
|
||||
}
|
||||
|
||||
previousTime = currentTime;
|
||||
|
||||
if (currentValue === null) {
|
||||
if (ignoreNulls) {
|
||||
continue;
|
||||
}
|
||||
if (nullAsZero) {
|
||||
currentValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentValue !== null) {
|
||||
if (_.isNumber(currentValue)) {
|
||||
total += currentValue;
|
||||
allIsNull = false;
|
||||
nonNulls++;
|
||||
}
|
||||
|
||||
if (currentValue > max) {
|
||||
max = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue < min) {
|
||||
min = currentValue;
|
||||
}
|
||||
|
||||
if (first === null) {
|
||||
first = currentValue;
|
||||
} else {
|
||||
if (previousValue > currentValue) {
|
||||
// counter reset
|
||||
previousDeltaUp = false;
|
||||
if (i === item.datapoints.length - 1) {
|
||||
// reset on last
|
||||
delta += currentValue;
|
||||
}
|
||||
} else {
|
||||
if (previousDeltaUp) {
|
||||
delta += currentValue - previousValue; // normal increment
|
||||
} else {
|
||||
delta += currentValue; // account for counter reset
|
||||
}
|
||||
previousDeltaUp = true;
|
||||
}
|
||||
}
|
||||
previousValue = currentValue;
|
||||
|
||||
if (currentValue < logmin && currentValue > 0) {
|
||||
logmin = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue !== 0) {
|
||||
allIsZero = false;
|
||||
}
|
||||
}
|
||||
|
||||
result.push([currentTime, currentValue]);
|
||||
}
|
||||
|
||||
if (max === -Number.MAX_VALUE) {
|
||||
max = null;
|
||||
}
|
||||
|
||||
if (min === Number.MAX_VALUE) {
|
||||
min = null;
|
||||
}
|
||||
|
||||
if (result.length && !allIsNull) {
|
||||
avg = total / nonNulls;
|
||||
current = result[result.length - 1][1];
|
||||
if (current === null && result.length > 1) {
|
||||
current = result[result.length - 2][1];
|
||||
}
|
||||
}
|
||||
|
||||
if (max !== null && min !== null) {
|
||||
range = max - min;
|
||||
}
|
||||
|
||||
if (current !== null && first !== null) {
|
||||
diff = current - first;
|
||||
}
|
||||
|
||||
const count = result.length;
|
||||
|
||||
return {
|
||||
data: result,
|
||||
label: label,
|
||||
color: colorPalette[colorIndex],
|
||||
stats: {
|
||||
total,
|
||||
min,
|
||||
max,
|
||||
current,
|
||||
logmin,
|
||||
avg,
|
||||
diff,
|
||||
delta,
|
||||
timeStep,
|
||||
range,
|
||||
count,
|
||||
first,
|
||||
allIsZero,
|
||||
allIsNull,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return vmSeries;
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
// Libraries
|
||||
import $ from 'jquery';
|
||||
import React, { PureComponent } from 'react';
|
||||
import 'vendor/flot/jquery.flot';
|
||||
import 'vendor/flot/jquery.flot.time';
|
||||
|
||||
// Types
|
||||
import { TimeRange, TimeSeriesVMs } from 'app/types';
|
||||
import { TimeRange, TimeSeriesVMs } from '../../types';
|
||||
|
||||
interface GraphProps {
|
||||
timeSeries: TimeSeriesVMs;
|
||||
@ -24,7 +22,7 @@ export class Graph extends PureComponent<GraphProps> {
|
||||
showBars: false,
|
||||
};
|
||||
|
||||
element: HTMLElement;
|
||||
element: HTMLElement | null;
|
||||
|
||||
componentDidUpdate() {
|
||||
this.draw();
|
||||
@ -35,6 +33,10 @@ export class Graph extends PureComponent<GraphProps> {
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.element === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, timeSeries, timeRange, showLines, showBars, showPoints } = this.props;
|
||||
|
||||
if (!width) {
|
||||
@ -76,7 +78,7 @@ export class Graph extends PureComponent<GraphProps> {
|
||||
max: max,
|
||||
label: 'Datetime',
|
||||
ticks: ticks,
|
||||
timeformat: time_format(ticks, min, max),
|
||||
timeformat: timeFormat(ticks, min, max),
|
||||
},
|
||||
grid: {
|
||||
minBorderMargin: 0,
|
||||
@ -109,7 +111,7 @@ export class Graph extends PureComponent<GraphProps> {
|
||||
}
|
||||
|
||||
// Copied from graph.ts
|
||||
function time_format(ticks, min, max) {
|
||||
function timeFormat(ticks: number, min: number, max: number): string {
|
||||
if (min && max && ticks) {
|
||||
const range = max - min;
|
||||
const secPerTick = range / ticks / 1000;
|
1
packages/grafana-ui/src/visualizations/index.ts
Normal file
1
packages/grafana-ui/src/visualizations/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Graph } from './Graph/Graph';
|
18
packages/grafana-ui/tsconfig.json
Normal file
18
packages/grafana-ui/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"module": "esnext",
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
3
packages/grafana-ui/tslint.json
Normal file
3
packages/grafana-ui/tslint.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
@ -292,6 +292,8 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
|
||||
Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1),
|
||||
}
|
||||
|
||||
a.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq))
|
||||
|
||||
searchResult, err = a.conn.Search(&searchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -76,7 +76,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
|
||||
return nil, models.ErrDashboardFolderCannotHaveParent
|
||||
}
|
||||
|
||||
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(models.RootFolderName) {
|
||||
if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) {
|
||||
return nil, models.ErrDashboardFolderNameExists
|
||||
}
|
||||
|
||||
@ -175,7 +175,9 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
|
||||
dto.User = &models.SignedInUser{
|
||||
UserId: 0,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
OrgId: dto.OrgId,
|
||||
}
|
||||
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -69,11 +70,14 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook *
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode/100 == 2 {
|
||||
// flushing the body enables the transport to reuse the same connection
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -33,14 +33,18 @@ type MysqlStore struct {
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
expiry int64
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// NewMysqlStore creates and returns a mysql session store.
|
||||
func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
|
||||
func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}, expiry int64) *MysqlStore {
|
||||
return &MysqlStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
expiry: expiry,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +54,7 @@ func (s *MysqlStore) Set(key, val interface{}) error {
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
s.dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -67,6 +72,7 @@ func (s *MysqlStore) Delete(key interface{}) error {
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
s.dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -77,13 +83,20 @@ func (s *MysqlStore) ID() string {
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *MysqlStore) Release() error {
|
||||
newExpiry := time.Now().Unix()
|
||||
if !s.dirty && (s.expiry+60) >= newExpiry {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
|
||||
data, time.Now().Unix(), s.sid)
|
||||
data, newExpiry, s.sid)
|
||||
s.dirty = false
|
||||
s.expiry = newExpiry
|
||||
return err
|
||||
}
|
||||
|
||||
@ -93,6 +106,7 @@ func (s *MysqlStore) Flush() error {
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
s.dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -117,11 +131,12 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
|
||||
expiry := time.Now().Unix()
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
|
||||
err := p.c.QueryRow("SELECT data,expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry)
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
|
||||
sid, "", time.Now().Unix())
|
||||
sid, "", expiry)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -137,7 +152,7 @@ func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return NewMysqlStore(p.c, sid, kv), nil
|
||||
return NewMysqlStore(p.c, sid, kv, expiry), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
|
@ -2,12 +2,47 @@ package migrator
|
||||
|
||||
type MigrationCondition interface {
|
||||
Sql(dialect Dialect) (string, []interface{})
|
||||
IsFulfilled(results []map[string][]byte) bool
|
||||
}
|
||||
|
||||
type IfTableExistsCondition struct {
|
||||
type ExistsMigrationCondition struct{}
|
||||
|
||||
func (c *ExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool {
|
||||
return len(results) >= 1
|
||||
}
|
||||
|
||||
type NotExistsMigrationCondition struct{}
|
||||
|
||||
func (c *NotExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool {
|
||||
return len(results) == 0
|
||||
}
|
||||
|
||||
type IfIndexExistsCondition struct {
|
||||
ExistsMigrationCondition
|
||||
TableName string
|
||||
IndexName string
|
||||
}
|
||||
|
||||
func (c *IfTableExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
|
||||
return dialect.TableCheckSql(c.TableName)
|
||||
func (c *IfIndexExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
|
||||
return dialect.IndexCheckSql(c.TableName, c.IndexName)
|
||||
}
|
||||
|
||||
type IfIndexNotExistsCondition struct {
|
||||
NotExistsMigrationCondition
|
||||
TableName string
|
||||
IndexName string
|
||||
}
|
||||
|
||||
func (c *IfIndexNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
|
||||
return dialect.IndexCheckSql(c.TableName, c.IndexName)
|
||||
}
|
||||
|
||||
type IfColumnNotExistsCondition struct {
|
||||
NotExistsMigrationCondition
|
||||
TableName string
|
||||
ColumnName string
|
||||
}
|
||||
|
||||
func (c *IfColumnNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
|
||||
return dialect.ColumnCheckSql(c.TableName, c.ColumnName)
|
||||
}
|
||||
|
@ -29,10 +29,12 @@ type Dialect interface {
|
||||
DropTable(tableName string) string
|
||||
DropIndexSql(tableName string, index *Index) string
|
||||
|
||||
TableCheckSql(tableName string) (string, []interface{})
|
||||
RenameTable(oldName string, newName string) string
|
||||
UpdateTableSql(tableName string, columns []*Column) string
|
||||
|
||||
IndexCheckSql(tableName, indexName string) (string, []interface{})
|
||||
ColumnCheckSql(tableName, columnName string) (string, []interface{})
|
||||
|
||||
ColString(*Column) string
|
||||
ColStringNoPk(*Column) string
|
||||
|
||||
@ -182,6 +184,10 @@ func (db *BaseDialect) RenameTable(oldName string, newName string) string {
|
||||
return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName))
|
||||
}
|
||||
|
||||
func (db *BaseDialect) ColumnCheckSql(tableName, columnName string) (string, []interface{}) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string {
|
||||
quote := db.dialect.Quote
|
||||
name := index.XName(tableName)
|
||||
|
@ -85,7 +85,9 @@ type AddColumnMigration struct {
|
||||
}
|
||||
|
||||
func NewAddColumnMigration(table Table, col *Column) *AddColumnMigration {
|
||||
return &AddColumnMigration{tableName: table.Name, column: col}
|
||||
m := &AddColumnMigration{tableName: table.Name, column: col}
|
||||
m.Condition = &IfColumnNotExistsCondition{TableName: table.Name, ColumnName: col.Name}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
|
||||
@ -109,7 +111,9 @@ type AddIndexMigration struct {
|
||||
}
|
||||
|
||||
func NewAddIndexMigration(table Table, index *Index) *AddIndexMigration {
|
||||
return &AddIndexMigration{tableName: table.Name, index: index}
|
||||
m := &AddIndexMigration{tableName: table.Name, index: index}
|
||||
m.Condition = &IfIndexNotExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration {
|
||||
@ -128,7 +132,9 @@ type DropIndexMigration struct {
|
||||
}
|
||||
|
||||
func NewDropIndexMigration(table Table, index *Index) *DropIndexMigration {
|
||||
return &DropIndexMigration{tableName: table.Name, index: index}
|
||||
m := &DropIndexMigration{tableName: table.Name, index: index}
|
||||
m.Condition = &IfIndexExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *DropIndexMigration) Sql(dialect Dialect) string {
|
||||
@ -179,11 +185,6 @@ func NewRenameTableMigration(oldName string, newName string) *RenameTableMigrati
|
||||
return &RenameTableMigration{oldName: oldName, newName: newName}
|
||||
}
|
||||
|
||||
func (m *RenameTableMigration) IfTableExists(tableName string) *RenameTableMigration {
|
||||
m.Condition = &IfTableExistsCondition{TableName: tableName}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *RenameTableMigration) Rename(oldName string, newName string) *RenameTableMigration {
|
||||
m.oldName = oldName
|
||||
m.newName = newName
|
||||
@ -212,11 +213,6 @@ func NewCopyTableDataMigration(targetTable string, sourceTable string, colMap ma
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataMigration {
|
||||
m.Condition = &IfTableExistsCondition{TableName: tableName}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *CopyTableDataMigration) Sql(d Dialect) string {
|
||||
return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols)
|
||||
}
|
||||
|
@ -94,8 +94,6 @@ func (mg *Migrator) Start() error {
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
mg.Logger.Debug("Executing", "sql", sql)
|
||||
|
||||
err := mg.inTransaction(func(sess *xorm.Session) error {
|
||||
err := mg.exec(m, sess)
|
||||
if err != nil {
|
||||
@ -123,18 +121,30 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
|
||||
condition := m.GetCondition()
|
||||
if condition != nil {
|
||||
sql, args := condition.Sql(mg.Dialect)
|
||||
results, err := sess.SQL(sql).Query(args...)
|
||||
if err != nil || len(results) == 0 {
|
||||
mg.Logger.Debug("Skipping migration condition not fulfilled", "id", m.Id())
|
||||
return sess.Rollback()
|
||||
|
||||
if sql != "" {
|
||||
mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args)
|
||||
results, err := sess.SQL(sql, args...).Query()
|
||||
if err != nil {
|
||||
mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !condition.IsFulfilled(results) {
|
||||
mg.Logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if codeMigration, ok := m.(CodeMigration); ok {
|
||||
mg.Logger.Debug("Executing code migration", "id", m.Id())
|
||||
err = codeMigration.Exec(sess, mg)
|
||||
} else {
|
||||
_, err = sess.Exec(m.Sql(mg.Dialect))
|
||||
sql := m.Sql(mg.Dialect)
|
||||
mg.Logger.Debug("Executing sql migration", "id", m.Id(), "sql", sql)
|
||||
_, err = sess.Exec(sql)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -90,12 +90,6 @@ func (db *Mysql) SqlType(c *Column) string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{"grafana", tableName}
|
||||
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string {
|
||||
var statements = []string{}
|
||||
|
||||
@ -108,6 +102,18 @@ func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string {
|
||||
return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";"
|
||||
}
|
||||
|
||||
func (db *Mysql) IndexCheckSql(tableName, indexName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, indexName}
|
||||
sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("STATISTICS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("INDEX_NAME") + "=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *Mysql) ColumnCheckSql(tableName, columnName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, columnName}
|
||||
sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("COLUMNS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("COLUMN_NAME") + "=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *Mysql) CleanDB() error {
|
||||
tables, _ := db.engine.DBMetas()
|
||||
sess := db.engine.NewSession()
|
||||
|
@ -101,9 +101,9 @@ func (db *Postgres) SqlType(c *Column) string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{"grafana", tableName}
|
||||
sql := "SELECT table_name FROM information_schema.tables WHERE table_schema=? and table_name=?"
|
||||
func (db *Postgres) IndexCheckSql(tableName, indexName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, indexName}
|
||||
sql := "SELECT 1 FROM " + db.Quote("pg_indexes") + " WHERE" + db.Quote("tablename") + "=? AND " + db.Quote("indexname") + "=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
|
@ -68,9 +68,10 @@ func (db *Sqlite3) SqlType(c *Column) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName}
|
||||
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
|
||||
func (db *Sqlite3) IndexCheckSql(tableName, indexName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName, indexName}
|
||||
sql := "SELECT 1 FROM " + db.Quote("sqlite_master") + " WHERE " + db.Quote("type") + "='index' AND " + db.Quote("tbl_name") + "=? AND " + db.Quote("name") + "=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string {
|
||||
|
@ -243,7 +243,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) {
|
||||
ss.dbCfg.Path = filepath.Join(ss.Cfg.DataPath, ss.dbCfg.Path)
|
||||
}
|
||||
os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm)
|
||||
cnnstr = "file:" + ss.dbCfg.Path + "?cache=shared&mode=rwc"
|
||||
cnnstr = fmt.Sprintf("file:%s?cache=%s&mode=rwc", ss.dbCfg.Path, ss.dbCfg.CacheMode)
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown database type: %s", ss.dbCfg.Type)
|
||||
}
|
||||
@ -319,6 +319,8 @@ func (ss *SqlStore) readConfig() {
|
||||
ss.dbCfg.ClientCertPath = sec.Key("client_cert_path").String()
|
||||
ss.dbCfg.ServerCertName = sec.Key("server_cert_name").String()
|
||||
ss.dbCfg.Path = sec.Key("path").MustString("data/grafana.db")
|
||||
|
||||
ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private")
|
||||
}
|
||||
|
||||
func InitTestDB(t *testing.T) *SqlStore {
|
||||
@ -391,7 +393,13 @@ func IsTestDbPostgres() bool {
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Type, Host, Name, User, Pwd, Path, SslMode string
|
||||
Type string
|
||||
Host string
|
||||
Name string
|
||||
User string
|
||||
Pwd string
|
||||
Path string
|
||||
SslMode string
|
||||
CaCertPath string
|
||||
ClientKeyPath string
|
||||
ClientCertPath string
|
||||
@ -400,4 +408,5 @@ type DatabaseConfig struct {
|
||||
MaxOpenConn int
|
||||
MaxIdleConn int
|
||||
ConnMaxLifetime int
|
||||
CacheMode string
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data
|
||||
rawQuery := model.Get("query").MustString("")
|
||||
useRawQuery := model.Get("rawQuery").MustBool(false)
|
||||
alias := model.Get("alias").MustString("")
|
||||
tz := model.Get("tz").MustString("")
|
||||
|
||||
measurement := model.Get("measurement").MustString("")
|
||||
|
||||
@ -55,6 +56,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data
|
||||
Interval: parsedInterval,
|
||||
Alias: alias,
|
||||
UseRawQuery: useRawQuery,
|
||||
Tz: tz,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ func TestInfluxdbQueryParser(t *testing.T) {
|
||||
}
|
||||
],
|
||||
"measurement": "logins.count",
|
||||
"tz": "Europe/Paris",
|
||||
"policy": "default",
|
||||
"refId": "B",
|
||||
"resultFormat": "time_series",
|
||||
@ -115,6 +116,7 @@ func TestInfluxdbQueryParser(t *testing.T) {
|
||||
So(len(res.GroupBy), ShouldEqual, 3)
|
||||
So(len(res.Selects), ShouldEqual, 3)
|
||||
So(len(res.Tags), ShouldEqual, 2)
|
||||
So(res.Tz, ShouldEqual, "Europe/Paris")
|
||||
So(res.Interval, ShouldEqual, time.Second*20)
|
||||
So(res.Alias, ShouldEqual, "serie alias")
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ type Query struct {
|
||||
UseRawQuery bool
|
||||
Alias string
|
||||
Interval time.Duration
|
||||
Tz string
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
|
@ -26,6 +26,7 @@ func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
|
||||
res += query.renderWhereClause()
|
||||
res += query.renderTimeFilter(queryContext)
|
||||
res += query.renderGroupBy(queryContext)
|
||||
res += query.renderTz()
|
||||
}
|
||||
|
||||
calculator := tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{})
|
||||
@ -154,3 +155,12 @@ func (query *Query) renderGroupBy(queryContext *tsdb.TsdbQuery) string {
|
||||
|
||||
return groupBy
|
||||
}
|
||||
|
||||
func (query *Query) renderTz() string {
|
||||
tz := query.Tz
|
||||
if tz == "" {
|
||||
return ""
|
||||
} else {
|
||||
return fmt.Sprintf(" tz('%s')", tz)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,20 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
|
||||
So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "policy"."cpu" WHERE time > now() - 5m GROUP BY time(10s) fill(null)`)
|
||||
})
|
||||
|
||||
Convey("can build query with tz", func() {
|
||||
query := &Query{
|
||||
Selects: []*Select{{*qp1, *qp2}},
|
||||
Measurement: "cpu",
|
||||
GroupBy: []*QueryPart{groupBy1},
|
||||
Tz: "Europe/Paris",
|
||||
Interval: time.Second * 5,
|
||||
}
|
||||
|
||||
rawQuery, err := query.Build(queryContext)
|
||||
So(err, ShouldBeNil)
|
||||
So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE time > now() - 5m GROUP BY time(5s) tz('Europe/Paris')`)
|
||||
})
|
||||
|
||||
Convey("can build query with group bys", func() {
|
||||
query := &Query{
|
||||
Selects: []*Select{{*qp1, *qp2}},
|
||||
|
@ -86,11 +86,11 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
|
@ -41,7 +41,7 @@ func TestMacroEngine(t *testing.T) {
|
||||
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
@ -138,7 +138,7 @@ func TestMacroEngine(t *testing.T) {
|
||||
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
@ -158,7 +158,7 @@ func TestMacroEngine(t *testing.T) {
|
||||
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
@ -168,5 +168,22 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a time range between 1960-02-01 07:00:00.5 and 1980-02-03 08:00:00.5", func() {
|
||||
from := time.Date(1960, 2, 1, 7, 0, 0, 500e6, time.UTC)
|
||||
to := time.Date(1980, 2, 3, 8, 0, 0, 500e6, time.UTC)
|
||||
timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
|
||||
|
||||
So(from.Format(time.RFC3339Nano), ShouldEqual, "1960-02-01T07:00:00.5Z")
|
||||
So(to.Format(time.RFC3339Nano), ShouldEqual, "1980-02-03T08:00:00.5Z")
|
||||
Convey("interpolate __timeFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano)))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
import React, { PureComponent, ReactNode, ReactElement } from 'react';
|
||||
import { Label } from './Label';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
interface Props {
|
||||
label?: ReactNode;
|
||||
labelClassName?: string;
|
||||
id?: string;
|
||||
children: ReactElement<any>;
|
||||
}
|
||||
|
||||
export class Element extends PureComponent<Props> {
|
||||
elementId: string = this.props.id || uniqueId('form-element-');
|
||||
|
||||
get elementLabel() {
|
||||
const { label, labelClassName } = this.props;
|
||||
|
||||
if (label) {
|
||||
return (
|
||||
<Label htmlFor={this.elementId} className={labelClassName}>
|
||||
{label}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get children() {
|
||||
const { children } = this.props;
|
||||
|
||||
return React.cloneElement(children, { id: this.elementId });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="our-custom-wrapper-class">
|
||||
{this.elementLabel}
|
||||
{this.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import React, { PureComponent, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
htmlFor?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export class Label extends PureComponent<Props> {
|
||||
render() {
|
||||
const { children, htmlFor, className } = this.props;
|
||||
|
||||
return (
|
||||
<label className={`custom-label-class ${className || ''}`} htmlFor={htmlFor}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,3 +1 @@
|
||||
export { Element } from './Element';
|
||||
export { Input } from './Input';
|
||||
export { Label } from './Label';
|
||||
|
@ -52,7 +52,11 @@ export const ToggleButton: SFC<ToggleButtonProps> = ({
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
return <Tooltip content={tooltip}>{button}</Tooltip>;
|
||||
return (
|
||||
<Tooltip content={tooltip} placement="bottom">
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return button;
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ function bootstrapTagsinput() {
|
||||
},
|
||||
});
|
||||
|
||||
select.on('itemAdded', event => {
|
||||
select.on('itemAdded', (event: any) => {
|
||||
if (scope.model.indexOf(event.item) === -1) {
|
||||
scope.model.push(event.item);
|
||||
if (scope.onTagsUpdated) {
|
||||
@ -85,7 +85,7 @@ function bootstrapTagsinput() {
|
||||
setColor(event.item, tagElement);
|
||||
});
|
||||
|
||||
select.on('itemRemoved', event => {
|
||||
select.on('itemRemoved', (event: any) => {
|
||||
const idx = scope.model.indexOf(event.item);
|
||||
if (idx !== -1) {
|
||||
scope.model.splice(idx, 1);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import config from 'app/core/config';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class LiveSrv {
|
||||
conn: any;
|
||||
|
@ -2,14 +2,23 @@ import _ from 'lodash';
|
||||
import { TimeSeries } from 'app/core/core';
|
||||
import colors, { getThemeColor } from 'app/core/utils/colors';
|
||||
|
||||
/**
|
||||
* Mapping of log level abbreviation to canonical log level.
|
||||
* Supported levels are reduce to limit color variation.
|
||||
*/
|
||||
export enum LogLevel {
|
||||
emerg = 'critical',
|
||||
alert = 'critical',
|
||||
crit = 'critical',
|
||||
critical = 'critical',
|
||||
warn = 'warning',
|
||||
warning = 'warning',
|
||||
err = 'error',
|
||||
eror = 'error',
|
||||
error = 'error',
|
||||
info = 'info',
|
||||
notice = 'info',
|
||||
dbug = 'debug',
|
||||
debug = 'debug',
|
||||
trace = 'trace',
|
||||
unkown = 'unkown',
|
||||
@ -81,7 +90,9 @@ export interface LogsStream {
|
||||
|
||||
export interface LogsStreamEntry {
|
||||
line: string;
|
||||
timestamp: string;
|
||||
ts: string;
|
||||
// Legacy, was renamed to ts
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface LogsStreamLabels {
|
||||
|
@ -14,7 +14,6 @@ const DEFAULT_EXPLORE_STATE: ExploreState = {
|
||||
datasourceError: null,
|
||||
datasourceLoading: null,
|
||||
datasourceMissing: false,
|
||||
datasourceName: '',
|
||||
exploreDatasources: [],
|
||||
graphInterval: 1000,
|
||||
history: [],
|
||||
@ -69,7 +68,7 @@ describe('state functions', () => {
|
||||
it('returns url parameter value for a state object', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
datasourceName: 'foo',
|
||||
initialDatasource: 'foo',
|
||||
range: {
|
||||
from: 'now-5h',
|
||||
to: 'now',
|
||||
@ -94,7 +93,7 @@ describe('state functions', () => {
|
||||
it('returns url parameter value for a state object', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
datasourceName: 'foo',
|
||||
initialDatasource: 'foo',
|
||||
range: {
|
||||
from: 'now-5h',
|
||||
to: 'now',
|
||||
@ -120,7 +119,7 @@ describe('state functions', () => {
|
||||
it('can parse the serialized state into the original state', () => {
|
||||
const state = {
|
||||
...DEFAULT_EXPLORE_STATE,
|
||||
datasourceName: 'foo',
|
||||
initialDatasource: 'foo',
|
||||
range: {
|
||||
from: 'now - 5h',
|
||||
to: 'now',
|
||||
@ -144,7 +143,7 @@ describe('state functions', () => {
|
||||
const resultState = {
|
||||
...rest,
|
||||
datasource: DEFAULT_EXPLORE_STATE.datasource,
|
||||
datasourceName: datasource,
|
||||
initialDatasource: datasource,
|
||||
initialQueries: queries,
|
||||
};
|
||||
|
||||
|
@ -9,7 +9,8 @@ import { parse as parseDate } from 'app/core/utils/datemath';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||
import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore';
|
||||
import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series';
|
||||
import { DataQuery, DataSourceApi } from 'app/types/series';
|
||||
import { RawTimeRange, IntervalValues } from '@grafana/ui';
|
||||
|
||||
export const DEFAULT_RANGE = {
|
||||
from: 'now-6h',
|
||||
@ -104,7 +105,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
||||
|
||||
export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
|
||||
const urlState: ExploreUrlState = {
|
||||
datasource: state.datasourceName,
|
||||
datasource: state.initialDatasource,
|
||||
queries: state.initialQueries.map(clearQueryKeys),
|
||||
range: state.range,
|
||||
};
|
||||
|
@ -629,6 +629,8 @@ kbn.valueFormats.conmgm3 = kbn.formatBuilders.fixedUnit('mg/m³');
|
||||
kbn.valueFormats.conmgNm3 = kbn.formatBuilders.fixedUnit('mg/Nm³');
|
||||
kbn.valueFormats.congm3 = kbn.formatBuilders.fixedUnit('g/m³');
|
||||
kbn.valueFormats.congNm3 = kbn.formatBuilders.fixedUnit('g/Nm³');
|
||||
kbn.valueFormats.conmgdL = kbn.formatBuilders.fixedUnit('mg/dL');
|
||||
kbn.valueFormats.conmmolL = kbn.formatBuilders.fixedUnit('mmol/L');
|
||||
|
||||
// Time
|
||||
kbn.valueFormats.hertz = kbn.formatBuilders.decimalSIPrefix('Hz');
|
||||
@ -1209,6 +1211,8 @@ kbn.getUnitFormats = () => {
|
||||
{ text: 'milligram per normal cubic meter (mg/Nm³)', value: 'conmgNm3' },
|
||||
{ text: 'gram per cubic meter (g/m³)', value: 'congm3' },
|
||||
{ text: 'gram per normal cubic meter (g/Nm³)', value: 'congNm3' },
|
||||
{ text: 'milligrams per decilitre (mg/dL)', value: 'conmgdL' },
|
||||
{ text: 'millimoles per litre (mmol/L)', value: 'conmmolL' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { RawTimeRange } from 'app/types/series';
|
||||
import { RawTimeRange } from '@grafana/ui';
|
||||
|
||||
import * as dateMath from './datemath';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import classNames from 'classnames/bind';
|
||||
import classNames from 'classnames';
|
||||
import { AlertRule } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
@ -23,7 +23,7 @@ class AlertRuleItem extends PureComponent<Props> {
|
||||
render() {
|
||||
const { rule, onTogglePause } = this.props;
|
||||
|
||||
const stateClass = classNames({
|
||||
const iconClassName = classNames({
|
||||
fa: true,
|
||||
'fa-play': rule.state === 'paused',
|
||||
'fa-pause': rule.state !== 'paused',
|
||||
@ -55,7 +55,7 @@ class AlertRuleItem extends PureComponent<Props> {
|
||||
title="Pausing an alert rule prevents it from executing"
|
||||
onClick={onTogglePause}
|
||||
>
|
||||
<i className={stateClass} />
|
||||
<i className={iconClassName} />
|
||||
</button>
|
||||
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
|
||||
<i className="icon-gf icon-gf-settings" />
|
||||
|
@ -13,7 +13,7 @@ import ApiKeysAddedModal from './ApiKeysAddedModal';
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
|
||||
import { DeleteButton } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
@ -224,7 +224,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
|
||||
<td>{key.name}</td>
|
||||
<td>{key.role}</td>
|
||||
<td>
|
||||
<DeleteButton onConfirmDelete={() => this.onDeleteApiKey(key)} />
|
||||
<DeleteButton onConfirm={() => this.onDeleteApiKey(key)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -160,14 +160,14 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<PanelResizer
|
||||
isEditing={!!isEditing}
|
||||
isEditing={isEditing}
|
||||
panel={panel}
|
||||
render={(panelHeight: number | 'inherit') => (
|
||||
render={styles => (
|
||||
<div
|
||||
className={panelWrapperClass}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
style={{ height: panelHeight }}
|
||||
style={styles}
|
||||
>
|
||||
{plugin.exports.Panel && this.renderReactPanel()}
|
||||
{plugin.exports.PanelCtrl && this.renderAngularPanel()}
|
||||
|
@ -8,7 +8,8 @@ import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
|
||||
// Types
|
||||
import { TimeRange, LoadingState, DataQueryOptions, DataQueryResponse, TimeSeries } from 'app/types';
|
||||
import { DataQueryOptions, DataQueryResponse } from 'app/types';
|
||||
import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
|
||||
|
||||
interface RenderProps {
|
||||
loading: LoadingState;
|
||||
|
@ -16,7 +16,8 @@ import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { PanelPlugin, TimeRange } from 'app/types';
|
||||
import { PanelPlugin } from 'app/types';
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
|
@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
|
||||
import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
import { PanelMenuItem } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { SFC } from 'react';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
import { PanelMenuItem } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
children: any;
|
||||
|
@ -1,6 +1,10 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PanelPlugin, PanelProps } from 'app/types';
|
||||
|
||||
// Types
|
||||
import { PanelProps } from '@grafana/ui';
|
||||
import { PanelPlugin } from 'app/types';
|
||||
|
||||
interface Props {
|
||||
pluginId: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { throttle } from 'lodash';
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
@ -6,7 +6,7 @@ import { PanelModel } from '../panel_model';
|
||||
|
||||
interface Props {
|
||||
isEditing: boolean;
|
||||
render: (height: number | 'inherit') => JSX.Element;
|
||||
render: (styles: object) => JSX.Element;
|
||||
panel: PanelModel;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ export class PanelResizer extends PureComponent<Props, State> {
|
||||
prevEditorHeight: number;
|
||||
throttledChangeHeight: (height: number) => void;
|
||||
throttledResizeDone: () => void;
|
||||
noStyles: object = {};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -65,7 +66,7 @@ export class PanelResizer extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
{render(isEditing ? editorHeight : 'inherit')}
|
||||
{render(isEditing ? {height: editorHeight} : this.noStyles)}
|
||||
{isEditing && (
|
||||
<div className="panel-editor-container__resizer">
|
||||
<Draggable axis="y" grid={[100, 1]} onDrag={this.onDrag} position={{ x: 0, y: 0 }}>
|
||||
|
@ -10,6 +10,7 @@ import { Input } from 'app/core/components/Form';
|
||||
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
||||
import { InputStatus } from 'app/core/components/Form/Input';
|
||||
import DataSourceOption from './DataSourceOption';
|
||||
import { GfFormLabel } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
@ -163,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
{this.renderOptions()}
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Relative time</span>
|
||||
<GfFormLabel>Relative time</GfFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
|
@ -6,6 +6,7 @@ function dashLinksContainer() {
|
||||
return {
|
||||
scope: {
|
||||
links: '=',
|
||||
dashboard: '=',
|
||||
},
|
||||
restrict: 'E',
|
||||
controller: 'DashLinksContainerCtrl',
|
||||
@ -20,6 +21,8 @@ function dashLink($compile, $sanitize, linkSrv) {
|
||||
restrict: 'E',
|
||||
link: (scope, elem) => {
|
||||
const link = scope.link;
|
||||
const dashboard = scope.dashboard;
|
||||
|
||||
let template =
|
||||
'<div class="gf-form">' +
|
||||
'<a class="pointer gf-form-label" data-placement="bottom"' +
|
||||
@ -76,7 +79,7 @@ function dashLink($compile, $sanitize, linkSrv) {
|
||||
}
|
||||
|
||||
update();
|
||||
scope.$on('refresh', update);
|
||||
dashboard.events.on('refresh', update, scope);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.dashboard.links.length > 0" >
|
||||
<dash-links-container links="ctrl.dashboard.links" class="gf-form-inline"></dash-links-container>
|
||||
<dash-links-container links="ctrl.dashboard.links" dashboard="ctrl.dashboard" class="gf-form-inline"></dash-links-container>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
@ -6,9 +6,9 @@ import _ from 'lodash';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
// Types
|
||||
|
||||
import { TimeRange } from 'app/types';
|
||||
// Types
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
|
||||
export class TimeSrv {
|
||||
time: any;
|
||||
|
@ -4,7 +4,7 @@ import { store } from 'app/store/store';
|
||||
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
import { PanelMenuItem } from '@grafana/ui';
|
||||
|
||||
export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
const onViewPanel = () => {
|
||||
|
@ -4,7 +4,7 @@ import store from 'app/core/store';
|
||||
// Models
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { TimeRange } from 'app/types/series';
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
|
||||
// Utils
|
||||
import { isString as _isString } from 'lodash';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames/bind';
|
||||
import classNames from 'classnames';
|
||||
import DataSourcesListItem from './DataSourcesListItem';
|
||||
import { DataSource } from 'app/types';
|
||||
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
QueryHintGetter,
|
||||
QueryHint,
|
||||
} from 'app/types/explore';
|
||||
import { TimeRange, DataQuery } from 'app/types/series';
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
import { DataQuery } from 'app/types/series';
|
||||
import store from 'app/core/store';
|
||||
import {
|
||||
DEFAULT_RANGE,
|
||||
@ -39,6 +40,8 @@ import ErrorBoundary from './ErrorBoundary';
|
||||
import { Alert } from './Error';
|
||||
import TimePicker, { parseTime } from './TimePicker';
|
||||
|
||||
const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
|
||||
|
||||
interface ExploreProps {
|
||||
datasourceSrv: DatasourceSrv;
|
||||
onChangeSplit: (split: boolean, state?: ExploreState) => void;
|
||||
@ -89,6 +92,10 @@ interface ExploreProps {
|
||||
export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
el: any;
|
||||
exploreEvents: Emitter;
|
||||
/**
|
||||
* Set via URL or local storage
|
||||
*/
|
||||
initialDatasource: string;
|
||||
/**
|
||||
* Current query expressions of the rows including their modifications, used for running queries.
|
||||
* Not kept in component state to prevent edit-render roundtrips.
|
||||
@ -114,6 +121,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
initialQueries = splitState.initialQueries;
|
||||
} else {
|
||||
const { datasource, queries, range } = props.urlState as ExploreUrlState;
|
||||
const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
|
||||
initialQueries = ensureQueries(queries);
|
||||
const initialRange = { from: parseTime(range.from), to: parseTime(range.to) } || { ...DEFAULT_RANGE };
|
||||
// Millies step for helper bar charts
|
||||
@ -123,10 +131,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
datasourceError: null,
|
||||
datasourceLoading: null,
|
||||
datasourceMissing: false,
|
||||
datasourceName: datasource,
|
||||
exploreDatasources: [],
|
||||
graphInterval: initialGraphInterval,
|
||||
graphResult: [],
|
||||
initialDatasource,
|
||||
initialQueries,
|
||||
history: [],
|
||||
logsResult: null,
|
||||
@ -150,7 +158,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
|
||||
async componentDidMount() {
|
||||
const { datasourceSrv } = this.props;
|
||||
const { datasourceName } = this.state;
|
||||
const { initialDatasource } = this.state;
|
||||
if (!datasourceSrv) {
|
||||
throw new Error('No datasource service passed as props.');
|
||||
}
|
||||
@ -164,10 +172,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
|
||||
if (datasources.length > 0) {
|
||||
this.setState({ datasourceLoading: true, exploreDatasources });
|
||||
// Priority: datasource in url, default datasource, first explore datasource
|
||||
// Priority for datasource preselection: URL, localstorage, default datasource
|
||||
let datasource;
|
||||
if (datasourceName) {
|
||||
datasource = await datasourceSrv.get(datasourceName);
|
||||
if (initialDatasource) {
|
||||
datasource = await datasourceSrv.get(initialDatasource);
|
||||
} else {
|
||||
datasource = await datasourceSrv.get();
|
||||
}
|
||||
@ -252,13 +260,15 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
datasourceLoading: false,
|
||||
datasourceName: datasource.name,
|
||||
initialDatasource: datasource.name,
|
||||
initialQueries: nextQueries,
|
||||
logsHighlighterExpressions: undefined,
|
||||
showingStartPage: Boolean(StartPage),
|
||||
},
|
||||
() => {
|
||||
if (datasourceError === null) {
|
||||
// Save last-used datasource
|
||||
store.set(LAST_USED_DATASOURCE_KEY, datasource.name);
|
||||
this.onSubmit();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import 'vendor/flot/jquery.flot.time';
|
||||
import 'vendor/flot/jquery.flot.selection';
|
||||
import 'vendor/flot/jquery.flot.stack';
|
||||
|
||||
import { RawTimeRange } from 'app/types/series';
|
||||
import { RawTimeRange } from '@grafana/ui';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
|
||||
|
@ -4,7 +4,7 @@ import Highlighter from 'react-highlight-words';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import * as rangeUtil from 'app/core/utils/rangeutil';
|
||||
import { RawTimeRange } from 'app/types/series';
|
||||
import { RawTimeRange } from '@grafana/ui';
|
||||
import {
|
||||
LogsDedupDescription,
|
||||
LogsDedupStrategy,
|
||||
|
@ -3,7 +3,7 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { getIntervals } from 'app/core/utils/explore';
|
||||
import { DataQuery } from 'app/types';
|
||||
import { RawTimeRange } from 'app/types/series';
|
||||
import { RawTimeRange } from '@grafana/ui';
|
||||
import { getTimeSrv } from 'app/features/dashboard/time_srv';
|
||||
import 'app/features/plugins/plugin_loader';
|
||||
|
||||
|
@ -4,6 +4,7 @@ import ReactDOM from 'react-dom';
|
||||
import { Change, Value } from 'slate';
|
||||
import { Editor } from 'slate-react';
|
||||
import Plain from 'slate-plain-serializer';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/explore';
|
||||
|
||||
@ -30,6 +31,7 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean {
|
||||
export interface QueryFieldProps {
|
||||
additionalPlugins?: any[];
|
||||
cleanText?: (text: string) => string;
|
||||
disabled?: boolean;
|
||||
initialQuery: string | null;
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
@ -78,7 +80,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
this.placeholdersBuffer = new PlaceholdersBuffer(props.initialQuery || '');
|
||||
|
||||
// Base plugins
|
||||
this.plugins = [ClearPlugin(), NewlinePlugin(), ...props.additionalPlugins].filter(p => p);
|
||||
this.plugins = [ClearPlugin(), NewlinePlugin(), ...(props.additionalPlugins || [])].filter(p => p);
|
||||
|
||||
this.state = {
|
||||
suggestions: [],
|
||||
@ -440,12 +442,17 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
};
|
||||
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const wrapperClassName = classnames('slate-query-field__wrapper', {
|
||||
'slate-query-field__wrapper--disabled': disabled,
|
||||
});
|
||||
return (
|
||||
<div className="slate-query-field-wrapper">
|
||||
<div className={wrapperClassName}>
|
||||
<div className="slate-query-field">
|
||||
{this.renderMenu()}
|
||||
<Editor
|
||||
autoCorrect={false}
|
||||
readOnly={this.props.disabled}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onChange={this.onChange}
|
||||
|
@ -7,7 +7,7 @@ import { Emitter } from 'app/core/utils/emitter';
|
||||
import QueryEditor from './QueryEditor';
|
||||
import QueryTransactionStatus from './QueryTransactionStatus';
|
||||
import { DataSource, DataQuery } from 'app/types';
|
||||
import { RawTimeRange } from 'app/types/series';
|
||||
import { RawTimeRange } from '@grafana/ui';
|
||||
|
||||
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
||||
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
||||
|
@ -3,7 +3,7 @@ import moment from 'moment';
|
||||
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import * as rangeUtil from 'app/core/utils/rangeutil';
|
||||
import { RawTimeRange, TimeRange } from 'app/types/series';
|
||||
import { RawTimeRange, TimeRange } from '@grafana/ui';
|
||||
|
||||
const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
export const DEFAULT_RANGE = {
|
||||
|
@ -198,11 +198,10 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
calculatePanelHeight() {
|
||||
if (this.panel.fullscreen) {
|
||||
const docHeight = $('.react-grid-layout').height();
|
||||
const editHeight = Math.floor(docHeight * 0.35);
|
||||
const fullscreenHeight = Math.floor(docHeight * 0.8);
|
||||
this.containerHeight = this.panel.isEditing ? editHeight : fullscreenHeight;
|
||||
if (this.panel.isEditing) {
|
||||
this.containerHeight = $('.panel-wrapper--edit').height();
|
||||
} else if (this.panel.fullscreen) {
|
||||
this.containerHeight = $('.panel-wrapper--view').height();
|
||||
} else {
|
||||
this.containerHeight = this.panel.gridPos.h * GRID_CELL_HEIGHT + (this.panel.gridPos.h - 1) * GRID_CELL_VMARGIN;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div class="panel-option-section__body">
|
||||
<div class="section">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Repat</span>
|
||||
<span class="gf-form-label width-9">Repeat</span>
|
||||
<dash-repeat-option panel="ctrl.panel"></dash-repeat-option>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.repeat">
|
||||
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
|
||||
<div class="panel-option-section">
|
||||
<div class="panel-option-section__header">Drildown Links</div>
|
||||
<div class="panel-option-section__header">Drilldown Links</div>
|
||||
<div class="panel-option-section__body">
|
||||
<panel-links-editor panel="ctrl.panel"></panel-links-editor>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { SFC } from 'react';
|
||||
import classNames from 'classnames/bind';
|
||||
import classNames from 'classnames';
|
||||
import PluginListItem from './PluginListItem';
|
||||
import { Plugin } from 'app/types';
|
||||
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
|
||||
|
@ -26,16 +26,10 @@ import * as ticks from 'app/core/utils/ticks';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import builtInPlugins from './built_in_plugins';
|
||||
import * as d3 from 'd3';
|
||||
import * as grafanaUI from '@grafana/ui';
|
||||
|
||||
// rxjs
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
// these imports add functions to Observable
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/from';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/combineAll';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
// add cache busting
|
||||
const bust = `?_cache=${Date.now()}`;
|
||||
@ -71,6 +65,7 @@ function exposeToPlugin(name: string, component: any) {
|
||||
});
|
||||
}
|
||||
|
||||
exposeToPlugin('@grafana/ui', grafanaUI);
|
||||
exposeToPlugin('lodash', _);
|
||||
exposeToPlugin('moment', moment);
|
||||
exposeToPlugin('jquery', jquery);
|
||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
|
||||
import { DeleteButton } from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
import { NavModel, Team } from '../../types';
|
||||
@ -58,7 +58,7 @@ export class TeamList extends PureComponent<Props, any> {
|
||||
<a href={teamUrl}>{team.memberCount}</a>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<DeleteButton onConfirmDelete={() => this.deleteTeam(team)} />
|
||||
<DeleteButton onConfirm={() => this.deleteTeam(team)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||
import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
|
||||
import { DeleteButton } from '@grafana/ui';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { TeamMember, User } from 'app/types';
|
||||
import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions';
|
||||
@ -76,7 +76,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
<td>{member.email}</td>
|
||||
{syncEnabled && this.renderLabels(member.labels)}
|
||||
<td className="text-right">
|
||||
<DeleteButton onConfirmDelete={() => this.onRemoveMember(member)} />
|
||||
<DeleteButton onConfirm={() => this.onRemoveMember(member)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -124,7 +124,7 @@ exports[`Render should render teams table 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -174,7 +174,7 @@ exports[`Render should render teams table 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -224,7 +224,7 @@ exports[`Render should render teams table 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -274,7 +274,7 @@ exports[`Render should render teams table 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -324,7 +324,7 @@ exports[`Render should render teams table 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -204,7 +204,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -229,7 +229,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -254,7 +254,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -279,7 +279,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -304,7 +304,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -441,7 +441,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -482,7 +482,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -523,7 +523,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -564,7 +564,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -605,7 +605,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
onConfirm={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames/bind';
|
||||
import classNames from 'classnames';
|
||||
import { setUsersSearchQuery } from './state/actions';
|
||||
import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
|
||||
|
||||
|
@ -256,6 +256,10 @@ export default class InfluxQuery {
|
||||
query += ' SLIMIT ' + target.slimit;
|
||||
}
|
||||
|
||||
if (target.tz) {
|
||||
query += " tz('" + target.tz + "')";
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.target.tz">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">tz</label>
|
||||
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.tz" spellcheck='false' placeholder="No Timezone" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">FORMAT AS</label>
|
||||
|
@ -100,6 +100,9 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
if (!this.target.slimit) {
|
||||
options.push(this.uiSegmentSrv.newSegment({ value: 'SLIMIT' }));
|
||||
}
|
||||
if (!this.target.tz) {
|
||||
options.push(this.uiSegmentSrv.newSegment({ value: 'tz' }));
|
||||
}
|
||||
if (this.target.orderByTime === 'ASC') {
|
||||
options.push(this.uiSegmentSrv.newSegment({ value: 'ORDER BY time DESC' }));
|
||||
}
|
||||
@ -124,6 +127,10 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
this.target.slimit = 10;
|
||||
break;
|
||||
}
|
||||
case 'tz': {
|
||||
this.target.tz = 'UTC';
|
||||
break;
|
||||
}
|
||||
case 'ORDER BY time DESC': {
|
||||
this.target.orderByTime = 'DESC';
|
||||
break;
|
||||
|
@ -2,14 +2,23 @@ import React from 'react';
|
||||
|
||||
const CHEAT_SHEET_ITEMS = [
|
||||
{
|
||||
title: 'Logs From a Job',
|
||||
title: 'See your logs',
|
||||
label: 'Start by selecting a log stream from the Log labels selector.',
|
||||
},
|
||||
{
|
||||
title: 'Logs from a "job"',
|
||||
expression: '{job="default/prometheus"}',
|
||||
label: 'Returns all log lines emitted by instances of this job.',
|
||||
},
|
||||
{
|
||||
title: 'Search For Text',
|
||||
expression: '{app="cassandra"} Maximum memory usage',
|
||||
label: 'Returns all log lines for the selector and highlights the given text in the results.',
|
||||
title: 'Combine stream selectors',
|
||||
expression: '{app="cassandra",namespace="prod"}',
|
||||
label: 'Returns all log lines from streams that have both labels.',
|
||||
},
|
||||
{
|
||||
title: 'Search for text',
|
||||
expression: '{app="cassandra"} (duration|latency)\\s*(=|is|of)\\s*[\\d\\.]+',
|
||||
label: 'Add a regular expression after the selector to filter for.',
|
||||
},
|
||||
];
|
||||
|
||||
@ -19,12 +28,14 @@ export default (props: any) => (
|
||||
{CHEAT_SHEET_ITEMS.map(item => (
|
||||
<div className="cheat-sheet-item" key={item.expression}>
|
||||
<div className="cheat-sheet-item__title">{item.title}</div>
|
||||
{item.expression && (
|
||||
<div
|
||||
className="cheat-sheet-item__expression"
|
||||
onClick={e => props.onClickExample({ refId: '1', expr: item.expression })}
|
||||
>
|
||||
<code>{item.expression}</code>
|
||||
</div>
|
||||
)}
|
||||
<div className="cheat-sheet-item__label">{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user