mirror of https://github.com/grafana/grafana
commit
7133b79928
@ -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" |
@ -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) |
||||
} |
@ -0,0 +1,4 @@ |
||||
# Shared build scripts |
||||
|
||||
Shared build scripts for plugins & internal packages. |
||||
|
@ -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" |
||||
} |
@ -0,0 +1,3 @@ |
||||
# Grafana (WIP) shared component library |
||||
|
||||
Used by internal & external plugins. |
@ -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" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
@import 'DeleteButton/DeleteButton'; |
@ -0,0 +1 @@ |
||||
export { DeleteButton } from './DeleteButton/DeleteButton'; |
@ -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> |
||||
); |
||||
}; |
@ -0,0 +1 @@ |
||||
export { GfFormLabel } from './GfFormLabel/GfFormLabel'; |
@ -0,0 +1 @@ |
||||
@import 'components/index'; |
@ -0,0 +1,5 @@ |
||||
export * from './components'; |
||||
export * from './visualizations'; |
||||
export * from './types'; |
||||
export * from './utils'; |
||||
export * from './forms'; |
@ -0,0 +1,3 @@ |
||||
export * from './series'; |
||||
export * from './time'; |
||||
export * from './panel'; |
@ -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; |
||||
} |
@ -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[]; |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -0,0 +1 @@ |
||||
export * from './processTimeSeries'; |
@ -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; |
||||
} |
@ -0,0 +1 @@ |
||||
export { Graph } from './Graph/Graph'; |
@ -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 |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": "../../tslint.json" |
||||
} |
@ -0,0 +1,83 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import Remarkable from 'remarkable'; |
||||
import { getBackendSrv } from '../../services/backend_srv'; |
||||
|
||||
interface Props { |
||||
plugin: { |
||||
name: string; |
||||
id: string; |
||||
}; |
||||
type: string; |
||||
} |
||||
|
||||
interface State { |
||||
isError: boolean; |
||||
isLoading: boolean; |
||||
help: string; |
||||
} |
||||
|
||||
export class PluginHelp extends PureComponent<Props, State> { |
||||
state = { |
||||
isError: false, |
||||
isLoading: false, |
||||
help: '', |
||||
}; |
||||
|
||||
componentDidMount(): void { |
||||
this.loadHelp(); |
||||
} |
||||
|
||||
constructPlaceholderInfo() { |
||||
return 'No plugin help or readme markdown file was found'; |
||||
} |
||||
|
||||
loadHelp = () => { |
||||
const { plugin, type } = this.props; |
||||
this.setState({ isLoading: true }); |
||||
|
||||
getBackendSrv() |
||||
.get(`/api/plugins/${plugin.id}/markdown/${type}`) |
||||
.then(response => { |
||||
const markdown = new Remarkable(); |
||||
const helpHtml = markdown.render(response); |
||||
|
||||
if (response === '' && type === 'help') { |
||||
this.setState({ |
||||
isError: false, |
||||
isLoading: false, |
||||
help: this.constructPlaceholderInfo(), |
||||
}); |
||||
} else { |
||||
this.setState({ |
||||
isError: false, |
||||
isLoading: false, |
||||
help: helpHtml, |
||||
}); |
||||
} |
||||
}) |
||||
.catch(() => { |
||||
this.setState({ |
||||
isError: true, |
||||
isLoading: false, |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const { type } = this.props; |
||||
const { isError, isLoading, help } = this.state; |
||||
|
||||
if (isLoading) { |
||||
return <h2>Loading help...</h2>; |
||||
} |
||||
|
||||
if (isError) { |
||||
return <h3>'Error occurred when loading help'</h3>; |
||||
} |
||||
|
||||
if (type === 'panel_help' && help === '') { |
||||
} |
||||
|
||||
return <div className="markdown-html" dangerouslySetInnerHTML={{ __html: help }} />; |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue