Merge branch 'main' into sdboyer/relocate-scuemata
This commit is contained in:
@@ -228,5 +228,4 @@ By default, the Grafana Server Admin is the only user who can create and manage
|
||||
1. Create a built-in role assignment and map `fixed:permissions:admin:edit` and `fixed:permissions:admin:read` fixed roles to the `Editor` built-in role.
|
||||
1. [Create a custom role]({{< ref "#create-your-custom-role" >}}) with `roles.builtin:add` and `roles:write` permissions, then create a built-in role assignment for `Editor` organization role.
|
||||
|
||||
|
||||
Note that any user with the ability to modify roles can only create, update or delete roles with permissions they themselves have been granted. For example, a user with the `Editor` role would be able to create and manage roles only with the permissions they have, or with a subset of them.
|
||||
|
||||
@@ -6,3 +6,5 @@
|
||||
export * from './schema/legend.gen';
|
||||
export * from './schema/tooltip.gen';
|
||||
export * from './schema/text.gen';
|
||||
export * from './schema/table.gen';
|
||||
export * from './schema/graph.gen';
|
||||
|
||||
+35
-51
@@ -1,88 +1,72 @@
|
||||
package grafanaschema
|
||||
|
||||
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum")
|
||||
PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum")
|
||||
DrawStyle: "line" | "bars" | "points" @cuetsy(targetType="enum")
|
||||
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(targetType="enum")
|
||||
ScaleDistribution: "linear" | "log" | "ordinal" @cuetsy(targetType="enum")
|
||||
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(targetType="enum")
|
||||
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum")
|
||||
PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum")
|
||||
DrawStyle: "line" | "bars" | "points" @cuetsy(targetType="enum")
|
||||
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(targetType="enum")
|
||||
ScaleDistribution: "linear" | "log" @cuetsy(targetType="enum")
|
||||
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(targetType="enum")
|
||||
StackingMode: "none" | "normal" | "percent" @cuetsy(targetType="enum")
|
||||
BarValueVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum")
|
||||
BarAlignment: -1 | 0 | 1 @cuetsy(targetType="enum",memberNames="Before|Center|After")
|
||||
ScaleOrientation: 0 | 1 @cuetsy(targetType="enum",memberNames="Horizontal|Vertical")
|
||||
ScaleDirection: 1 | 1 | -1 | -1 @cuetsy(targetType="enum",memberNames="Up|Right|Down|Left")
|
||||
|
||||
LineStyle: {
|
||||
fill?: "solid" | "dash" | "dot" | "square"
|
||||
dash?: [...number]
|
||||
fill?: "solid" | "dash" | "dot" | "square"
|
||||
dash?: [...number]
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
LineConfig: {
|
||||
lineColor?: string
|
||||
lineWidth?: number
|
||||
lineInterpolation?: LineInterpolation
|
||||
lineStyle?: LineStyle
|
||||
spanNulls?: bool | number
|
||||
lineColor?: string
|
||||
lineWidth?: number
|
||||
lineInterpolation?: LineInterpolation
|
||||
lineStyle?: LineStyle
|
||||
spanNulls?: bool | number
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
BarConfig: {
|
||||
barAlignment?: BarAlignment
|
||||
barWidthFactor?: number
|
||||
barMaxWidth?: number
|
||||
barAlignment?: BarAlignment
|
||||
barWidthFactor?: number
|
||||
barMaxWidth?: number
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
FillConfig: {
|
||||
fillColor?: string
|
||||
fillOpacity?: number
|
||||
fillBelowTo?: string
|
||||
fillColor?: string
|
||||
fillOpacity?: number
|
||||
fillBelowTo?: string
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
PointsConfig: {
|
||||
showPoints?: PointVisibility
|
||||
pointSize?: number
|
||||
pointColor?: string
|
||||
pointSymbol?: string
|
||||
showPoints?: PointVisibility
|
||||
pointSize?: number
|
||||
pointColor?: string
|
||||
pointSymbol?: string
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
ScaleDistributionConfig: {
|
||||
type: ScaleDistribution
|
||||
log?: number
|
||||
type: ScaleDistribution
|
||||
log?: number
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
AxisConfig: {
|
||||
axisPlacement?: AxisPlacement
|
||||
axisLabel?: string
|
||||
axisWidth?: number
|
||||
axisSoftMin?: number
|
||||
axisSoftMax?: number
|
||||
scaleDistribution?: ScaleDistributionConfig
|
||||
axisPlacement?: AxisPlacement
|
||||
axisLabel?: string
|
||||
axisWidth?: number
|
||||
axisSoftMin?: number
|
||||
axisSoftMax?: number
|
||||
scaleDistribution?: ScaleDistributionConfig
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
HideSeriesConfig: {
|
||||
tooltip: bool
|
||||
legend: bool
|
||||
viz: bool
|
||||
tooltip: bool
|
||||
legend: bool
|
||||
viz: bool
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
StackingConfig: {
|
||||
mode?: StackingMode
|
||||
group?: string
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
StackableFieldConfig: {
|
||||
stacking?: StackingConfig
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
HideableFieldConfig: {
|
||||
hideFrom?: HideSeriesConfig
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
GraphTresholdsStyleMode: "off" | "line" | "area" | "line+area" | "series" @cuetsy(targetType="enum",memberNames="Off|Line|Area|LineAndArea|Series")
|
||||
|
||||
GraphThresholdsStyleConfig: {
|
||||
mode: GraphTresholdsStyleMode
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
GraphFieldConfig: {
|
||||
LineConfig
|
||||
FillConfig
|
||||
@@ -94,4 +78,4 @@ GraphFieldConfig: {
|
||||
drawStyle?: DrawStyle
|
||||
gradientMode?: GraphGradientMode
|
||||
thresholdsStyle?: GraphThresholdsStyleConfig
|
||||
} @cuetsy(targetType="interface")
|
||||
} @cuetsy(targetType="interface")
|
||||
@@ -0,0 +1,213 @@
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// NOTE: This file will be auto generated from models.cue
|
||||
// It is currenty hand written but will serve as the target for cuetsy
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
export enum AxisPlacement {
|
||||
Auto = 'auto',
|
||||
Bottom = 'bottom',
|
||||
Hidden = 'hidden',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
Top = 'top',
|
||||
}
|
||||
export enum PointVisibility {
|
||||
Always = 'always',
|
||||
Auto = 'auto',
|
||||
Never = 'never',
|
||||
}
|
||||
export enum DrawStyle {
|
||||
Bars = 'bars',
|
||||
Line = 'line',
|
||||
Points = 'points',
|
||||
}
|
||||
export enum LineInterpolation {
|
||||
Linear = 'linear',
|
||||
Smooth = 'smooth',
|
||||
StepAfter = 'stepAfter',
|
||||
StepBefore = 'stepBefore',
|
||||
}
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum ScaleDistribution {
|
||||
Linear = 'linear',
|
||||
Log = 'log',
|
||||
Ordinal = 'ordinal',
|
||||
}
|
||||
export enum GraphGradientMode {
|
||||
Hue = 'hue',
|
||||
None = 'none',
|
||||
Opacity = 'opacity',
|
||||
Scheme = 'scheme',
|
||||
}
|
||||
export interface LineStyle {
|
||||
dash?: number[];
|
||||
fill?: 'solid' | 'dash' | 'dot' | 'square';
|
||||
}
|
||||
|
||||
export interface PointsConfig {
|
||||
pointColor?: string;
|
||||
pointSize?: number;
|
||||
pointSymbol?: string;
|
||||
showPoints?: PointVisibility;
|
||||
}
|
||||
export interface ScaleDistributionConfig {
|
||||
log?: number;
|
||||
type: ScaleDistribution;
|
||||
}
|
||||
export interface HideSeriesConfig {
|
||||
viz: boolean;
|
||||
legend: boolean;
|
||||
tooltip: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum BarAlignment {
|
||||
Before = -1,
|
||||
Center = 0,
|
||||
After = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum BarValueVisibility {
|
||||
Auto = 'auto',
|
||||
Never = 'never',
|
||||
Always = 'always',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum ScaleOrientation {
|
||||
Horizontal = 0,
|
||||
Vertical = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
|
||||
export enum ScaleDirection {
|
||||
Up = 1,
|
||||
Right = 1,
|
||||
Down = -1,
|
||||
Left = -1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface LineConfig {
|
||||
lineColor?: string;
|
||||
lineWidth?: number;
|
||||
lineInterpolation?: LineInterpolation;
|
||||
lineStyle?: LineStyle;
|
||||
|
||||
/**
|
||||
* Indicate if null values should be treated as gaps or connected.
|
||||
* When the value is a number, it represents the maximum delta in the
|
||||
* X axis that should be considered connected. For timeseries, this is milliseconds
|
||||
*/
|
||||
spanNulls?: boolean | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface BarConfig {
|
||||
barAlignment?: BarAlignment;
|
||||
barWidthFactor?: number;
|
||||
barMaxWidth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface FillConfig {
|
||||
fillColor?: string;
|
||||
fillOpacity?: number;
|
||||
fillBelowTo?: string; // name of the field
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Axis is actually unique based on the unit... not each field!
|
||||
*/
|
||||
export interface AxisConfig {
|
||||
axisPlacement?: AxisPlacement;
|
||||
axisLabel?: string;
|
||||
axisWidth?: number; // pixels ideally auto?
|
||||
axisSoftMin?: number;
|
||||
axisSoftMax?: number;
|
||||
scaleDistribution?: ScaleDistributionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface HideableFieldConfig {
|
||||
hideFrom?: HideSeriesConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum StackingMode {
|
||||
None = 'none',
|
||||
Normal = 'normal',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StackingConfig {
|
||||
mode?: StackingMode;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StackableFieldConfig {
|
||||
stacking?: StackingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum GraphTresholdsStyleMode {
|
||||
Off = 'off',
|
||||
Line = 'line',
|
||||
Area = 'area',
|
||||
LineAndArea = 'line+area',
|
||||
Series = 'series',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface GraphThresholdsStyleConfig {
|
||||
mode: GraphTresholdsStyleMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface GraphFieldConfig
|
||||
extends LineConfig,
|
||||
FillConfig,
|
||||
PointsConfig,
|
||||
AxisConfig,
|
||||
BarConfig,
|
||||
StackableFieldConfig,
|
||||
HideableFieldConfig {
|
||||
drawStyle?: DrawStyle;
|
||||
gradientMode?: GraphGradientMode;
|
||||
thresholdsStyle?: GraphThresholdsStyleConfig;
|
||||
}
|
||||
+1
-8
@@ -1,5 +1,4 @@
|
||||
package grafanaschema
|
||||
|
||||
// TODO -- should not be table specific!
|
||||
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(targetType="type")
|
||||
|
||||
TableCellDisplayMode: {
|
||||
@@ -20,9 +19,3 @@ TableFieldOptions: {
|
||||
displayMode: TableCellDisplayMode | *"auto"
|
||||
hidden?: bool // ?? default is missing or false ??
|
||||
} @cuetsy(targetType="interface")
|
||||
|
||||
|
||||
TableSortByFieldState: {
|
||||
displayName: string
|
||||
desc?: bool
|
||||
} @cuetsy(targetType="interface")
|
||||
@@ -0,0 +1,21 @@
|
||||
export interface TableFieldOptions {
|
||||
width: number;
|
||||
minWidth: number;
|
||||
align: FieldTextAlignment;
|
||||
displayMode: TableCellDisplayMode;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export enum TableCellDisplayMode {
|
||||
Auto = 'auto',
|
||||
ColorText = 'color-text',
|
||||
ColorBackground = 'color-background',
|
||||
ColorBackgroundSolid = 'color-background-solid',
|
||||
GradientGauge = 'gradient-gauge',
|
||||
LcdGauge = 'lcd-gauge',
|
||||
JSONView = 'json-view',
|
||||
BasicGauge = 'basic',
|
||||
Image = 'image',
|
||||
}
|
||||
|
||||
export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center';
|
||||
@@ -9,7 +9,7 @@ import { calculateFontSize } from '../../utils/measureText';
|
||||
// Types
|
||||
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
import { DrawStyle, GraphFieldConfig } from '../uPlot/config';
|
||||
import { DrawStyle, GraphFieldConfig } from '@grafana/schema';
|
||||
import { Sparkline } from '../Sparkline/Sparkline';
|
||||
|
||||
const LINE_HEIGHT = 1.2;
|
||||
|
||||
@@ -276,7 +276,7 @@ In a nutshell, the two most important changes are:
|
||||
|
||||
```jsx
|
||||
- <input ref={register({ required: true })} name="test" />
|
||||
+ <input {...register('test', { required: true })} />
|
||||
+ <input {...register('test', { required: true })} />;
|
||||
```
|
||||
|
||||
- `InputControl`'s `as` prop has been replaced with `render`, which has `field` and `fieldState` objects as arguments. `onChange`, `onBlur`, `value`, `name`, and `ref` are parts of `field`.
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
LineInterpolation,
|
||||
PointVisibility,
|
||||
StackingMode,
|
||||
} from '..';
|
||||
} from '@grafana/schema';
|
||||
|
||||
function mockDataFrame() {
|
||||
const df1 = new MutableDataFrame({
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
PointVisibility,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '../uPlot/config';
|
||||
} from '@grafana/schema';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
import { Themeable2 } from '../../types';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DataFrame, FieldConfig, FieldSparkline, IndexVector } from '@grafana/data';
|
||||
import { GraphFieldConfig } from '../uPlot/config';
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
|
||||
/** @internal
|
||||
* Given a sparkline config returns a DataFrame ready to be turned into Plot data set
|
||||
|
||||
@@ -3,27 +3,7 @@ import { Field } from '@grafana/data';
|
||||
import { TableStyles } from './styles';
|
||||
import { CSSProperties, FC } from 'react';
|
||||
|
||||
export interface TableFieldOptions {
|
||||
width: number;
|
||||
minWidth: number;
|
||||
align: FieldTextAlignment;
|
||||
displayMode: TableCellDisplayMode;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export enum TableCellDisplayMode {
|
||||
Auto = 'auto',
|
||||
ColorText = 'color-text',
|
||||
ColorBackground = 'color-background',
|
||||
ColorBackgroundSolid = 'color-background-solid',
|
||||
GradientGauge = 'gradient-gauge',
|
||||
LcdGauge = 'lcd-gauge',
|
||||
JSONView = 'json-view',
|
||||
BasicGauge = 'basic',
|
||||
Image = 'image',
|
||||
}
|
||||
|
||||
export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center';
|
||||
export { TableFieldOptions, TableCellDisplayMode, FieldTextAlignment } from '@grafana/schema';
|
||||
|
||||
export interface TableRow {
|
||||
[x: string]: any;
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
PointVisibility,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '../uPlot/config';
|
||||
} from '@grafana/schema';
|
||||
import { collectStackingGroups, preparePlotData } from '../uPlot/utils';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
|
||||
@@ -237,7 +237,6 @@ export { LegacyForms, LegacyInputStatus };
|
||||
|
||||
// WIP, need renames and exports cleanup
|
||||
export * from './uPlot/config';
|
||||
export { ScaleDistribution } from './uPlot/models.gen';
|
||||
export { UPlotConfigBuilder, UPlotConfigPrepFn } from './uPlot/config/UPlotConfigBuilder';
|
||||
export { UPlotChart } from './uPlot/Plot';
|
||||
export { PlotLegend } from './uPlot/PlotLegend';
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { UPlotChart } from './Plot';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data';
|
||||
import { GraphFieldConfig, DrawStyle } from '../uPlot/config';
|
||||
import { GraphFieldConfig, DrawStyle } from '@grafana/schema';
|
||||
import uPlot from 'uplot';
|
||||
import createMockRaf from 'mock-raf';
|
||||
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||
import { VizLegendItem } from '../VizLegend/types';
|
||||
import { VizLegendOptions } from '@grafana/schema';
|
||||
import { AxisPlacement } from './config';
|
||||
import { VizLegendOptions, AxisPlacement } from '@grafana/schema';
|
||||
import { VizLayout, VizLayoutLegendProps } from '../VizLayout/VizLayout';
|
||||
import { VizLegend } from '../VizLegend/VizLegend';
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
@@ -1,239 +1,14 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ScaleDistribution } from './models.gen';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum AxisPlacement {
|
||||
Auto = 'auto', // First axis on the left, the rest on the right
|
||||
Top = 'top',
|
||||
Right = 'right',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
Hidden = 'hidden',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum PointVisibility {
|
||||
Auto = 'auto', // will show points when the density is low or line is hidden
|
||||
Never = 'never',
|
||||
Always = 'always',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum DrawStyle {
|
||||
Line = 'line', // default
|
||||
Bars = 'bars', // will also have a gap percent
|
||||
Points = 'points', // Only show points
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum LineInterpolation {
|
||||
Linear = 'linear',
|
||||
Smooth = 'smooth',
|
||||
StepBefore = 'stepBefore',
|
||||
StepAfter = 'stepAfter',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum BarAlignment {
|
||||
Before = -1,
|
||||
Center = 0,
|
||||
After = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum BarValueVisibility {
|
||||
Auto = 'auto',
|
||||
Never = 'never',
|
||||
Always = 'always',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum ScaleOrientation {
|
||||
Horizontal = 0,
|
||||
Vertical = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
|
||||
export enum ScaleDirection {
|
||||
Up = 1,
|
||||
Right = 1,
|
||||
Down = -1,
|
||||
Left = -1,
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface LineStyle {
|
||||
fill?: 'solid' | 'dash' | 'dot' | 'square';
|
||||
dash?: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface LineConfig {
|
||||
lineColor?: string;
|
||||
lineWidth?: number;
|
||||
lineInterpolation?: LineInterpolation;
|
||||
lineStyle?: LineStyle;
|
||||
|
||||
/**
|
||||
* Indicate if null values should be treated as gaps or connected.
|
||||
* When the value is a number, it represents the maximum delta in the
|
||||
* X axis that should be considered connected. For timeseries, this is milliseconds
|
||||
*/
|
||||
spanNulls?: boolean | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface BarConfig {
|
||||
barAlignment?: BarAlignment;
|
||||
barWidthFactor?: number;
|
||||
barMaxWidth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface FillConfig {
|
||||
fillColor?: string;
|
||||
fillOpacity?: number;
|
||||
fillBelowTo?: string; // name of the field
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum GraphGradientMode {
|
||||
None = 'none',
|
||||
Opacity = 'opacity',
|
||||
Hue = 'hue',
|
||||
Scheme = 'scheme',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface PointsConfig {
|
||||
showPoints?: PointVisibility;
|
||||
pointSize?: number;
|
||||
pointSymbol?: string; // eventually dot,star, etc
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface ScaleDistributionConfig {
|
||||
type: ScaleDistribution;
|
||||
log?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Axis is actually unique based on the unit... not each field!
|
||||
*/
|
||||
export interface AxisConfig {
|
||||
axisPlacement?: AxisPlacement;
|
||||
axisLabel?: string;
|
||||
axisWidth?: number; // pixels ideally auto?
|
||||
axisSoftMin?: number;
|
||||
axisSoftMax?: number;
|
||||
scaleDistribution?: ScaleDistributionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface HideSeriesConfig {
|
||||
tooltip: boolean;
|
||||
legend: boolean;
|
||||
viz: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface HideableFieldConfig {
|
||||
hideFrom?: HideSeriesConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum StackingMode {
|
||||
None = 'none',
|
||||
Normal = 'normal',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StackingConfig {
|
||||
mode?: StackingMode;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StackableFieldConfig {
|
||||
stacking?: StackingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum GraphTresholdsStyleMode {
|
||||
Off = 'off',
|
||||
Line = 'line',
|
||||
Area = 'area',
|
||||
LineAndArea = 'line+area',
|
||||
Series = 'series',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface GraphThresholdsStyleConfig {
|
||||
mode: GraphTresholdsStyleMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface GraphFieldConfig
|
||||
extends LineConfig,
|
||||
FillConfig,
|
||||
PointsConfig,
|
||||
AxisConfig,
|
||||
BarConfig,
|
||||
StackableFieldConfig,
|
||||
HideableFieldConfig {
|
||||
drawStyle?: DrawStyle;
|
||||
gradientMode?: GraphGradientMode;
|
||||
thresholdsStyle?: GraphThresholdsStyleConfig;
|
||||
}
|
||||
import {
|
||||
AxisPlacement,
|
||||
BarAlignment,
|
||||
DrawStyle,
|
||||
GraphGradientMode,
|
||||
GraphTresholdsStyleMode,
|
||||
LineInterpolation,
|
||||
PointVisibility,
|
||||
StackingMode,
|
||||
} from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -2,7 +2,7 @@ import { dateTimeFormat, GrafanaTheme2, isBooleanUnit, systemDateFormats, TimeZo
|
||||
import uPlot, { Axis } from 'uplot';
|
||||
import { PlotConfigBuilder } from '../types';
|
||||
import { measureText } from '../../../utils/measureText';
|
||||
import { AxisPlacement } from '../config';
|
||||
import { AxisPlacement } from '@grafana/schema';
|
||||
import { optMinMax } from './UPlotScaleBuilder';
|
||||
|
||||
export interface AxisProps {
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
ScaleOrientation,
|
||||
ScaleDirection,
|
||||
GraphTresholdsStyleMode,
|
||||
} from '../config';
|
||||
ScaleDistribution,
|
||||
} from '@grafana/schema';
|
||||
import { createTheme, ThresholdsMode } from '@grafana/data';
|
||||
import { ScaleDistribution } from '../models.gen';
|
||||
|
||||
describe('UPlotConfigBuilder', () => {
|
||||
const darkTheme = createTheme();
|
||||
|
||||
@@ -13,7 +13,7 @@ import { PlotConfig, PlotTooltipInterpolator } from '../types';
|
||||
import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
|
||||
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
|
||||
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
||||
import { AxisPlacement } from '../config';
|
||||
import { AxisPlacement } from '@grafana/schema';
|
||||
import { pluginLog } from '../utils';
|
||||
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uPlot, { Scale, Range } from 'uplot';
|
||||
import { PlotConfigBuilder } from '../types';
|
||||
import { ScaleOrientation, ScaleDirection } from '../config';
|
||||
import { ScaleDistribution } from '../models.gen';
|
||||
import { ScaleOrientation, ScaleDirection, ScaleDistribution } from '@grafana/schema';
|
||||
import { isBooleanUnit } from '@grafana/data';
|
||||
|
||||
export interface ScaleProps {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
LineInterpolation,
|
||||
PointsConfig,
|
||||
PointVisibility,
|
||||
} from '../config';
|
||||
} from '@grafana/schema';
|
||||
import { PlotConfigBuilder } from '../types';
|
||||
import { getHueGradientFn, getOpacityGradientFn, getScaleGradientFn } from './gradientFills';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '../config';
|
||||
import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '@grafana/schema';
|
||||
import { getDataRange, GradientDirection, scaleGradient } from './gradientFills';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
export interface UPlotThresholdOptions {
|
||||
scaleKey: string;
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// NOTE: This file will be auto generated from models.cue
|
||||
// It is currenty hand written but will serve as the target for cuetsy
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
export enum AxisPlacement {
|
||||
Auto = 'auto',
|
||||
Bottom = 'bottom',
|
||||
Hidden = 'hidden',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
Top = 'top',
|
||||
}
|
||||
export enum PointVisibility {
|
||||
Always = 'always',
|
||||
Auto = 'auto',
|
||||
Never = 'never',
|
||||
}
|
||||
export enum DrawStyle {
|
||||
Bars = 'bars',
|
||||
Line = 'line',
|
||||
Points = 'points',
|
||||
}
|
||||
export enum LineInterpolation {
|
||||
Linear = 'linear',
|
||||
Smooth = 'smooth',
|
||||
StepAfter = 'stepAfter',
|
||||
StepBefore = 'stepBefore',
|
||||
}
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export enum ScaleDistribution {
|
||||
Linear = 'linear',
|
||||
Log = 'log',
|
||||
Ordinal = 'ordinal',
|
||||
}
|
||||
export enum GraphGradientMode {
|
||||
Hue = 'hue',
|
||||
None = 'none',
|
||||
Opacity = 'opacity',
|
||||
Scheme = 'scheme',
|
||||
}
|
||||
export interface LineStyle {
|
||||
dash?: number[];
|
||||
fill?: 'solid' | 'dash' | 'dot' | 'square';
|
||||
}
|
||||
export interface LineConfig {
|
||||
lineColor?: string;
|
||||
lineInterpolation?: LineInterpolation;
|
||||
lineStyle?: LineStyle;
|
||||
lineWidth?: number;
|
||||
spanNulls?: boolean;
|
||||
}
|
||||
export interface FillConfig {
|
||||
fillBelowTo?: string;
|
||||
fillColor?: string;
|
||||
fillOpacity?: number;
|
||||
}
|
||||
export interface PointsConfig {
|
||||
pointColor?: string;
|
||||
pointSize?: number;
|
||||
pointSymbol?: string;
|
||||
showPoints?: PointVisibility;
|
||||
}
|
||||
export interface ScaleDistributionConfig {
|
||||
log?: number;
|
||||
type: ScaleDistribution;
|
||||
}
|
||||
export interface AxisConfig {
|
||||
axisLabel?: string;
|
||||
axisPlacement?: AxisPlacement;
|
||||
axisSoftMax?: number;
|
||||
axisSoftMin?: number;
|
||||
axisWidth?: number;
|
||||
scaleDistribution?: ScaleDistributionConfig;
|
||||
}
|
||||
export interface HideSeriesConfig {
|
||||
graph: boolean;
|
||||
legend: boolean;
|
||||
tooltip: boolean;
|
||||
}
|
||||
export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig {
|
||||
drawStyle?: DrawStyle;
|
||||
gradientMode?: GraphGradientMode;
|
||||
hideFrom?: HideSeriesConfig;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { preparePlotData, timeFormatToTemplate } from './utils';
|
||||
import { FieldType, MutableDataFrame } from '@grafana/data';
|
||||
import { StackingMode } from './config';
|
||||
import { StackingMode } from '@grafana/schema';
|
||||
|
||||
describe('timeFormatToTemplate', () => {
|
||||
it.each`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DataFrame, dateTime, Field, FieldType } from '@grafana/data';
|
||||
import { StackingMode } from './config';
|
||||
import { StackingMode } from '@grafana/schema';
|
||||
import { createLogger } from '../../utils/logger';
|
||||
import { attachDebugger } from '../../utils';
|
||||
import { AlignedData, Options, PaddingSide } from 'uplot';
|
||||
|
||||
@@ -6,16 +6,8 @@ import {
|
||||
identityOverrideProcessor,
|
||||
SelectableValue,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
AxisConfig,
|
||||
AxisPlacement,
|
||||
graphFieldOptions,
|
||||
ScaleDistributionConfig,
|
||||
Select,
|
||||
HorizontalGroup,
|
||||
RadioButtonGroup,
|
||||
} from '../../index';
|
||||
import { ScaleDistribution } from '../../components/uPlot/models.gen';
|
||||
import { graphFieldOptions, Select, HorizontalGroup, RadioButtonGroup } from '../../index';
|
||||
import { AxisConfig, AxisPlacement, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { startCase } from 'lodash';
|
||||
import { FilterPill, HorizontalGroup } from '../../index';
|
||||
import { FieldConfigEditorBuilder, FieldConfigEditorProps } from '@grafana/data';
|
||||
import { HideableFieldConfig, HideSeriesConfig } from '../../components/uPlot/config';
|
||||
import { HideableFieldConfig, HideSeriesConfig } from '@grafana/schema';
|
||||
|
||||
const SeriesConfigEditor: React.FC<FieldConfigEditorProps<HideSeriesConfig, {}>> = (props) => {
|
||||
const { value, onChange } = props;
|
||||
|
||||
@@ -5,16 +5,8 @@ import {
|
||||
identityOverrideProcessor,
|
||||
} from '@grafana/data';
|
||||
import React from 'react';
|
||||
import {
|
||||
graphFieldOptions,
|
||||
HorizontalGroup,
|
||||
IconButton,
|
||||
Input,
|
||||
RadioButtonGroup,
|
||||
StackingConfig,
|
||||
StackingMode,
|
||||
Tooltip,
|
||||
} from '../..';
|
||||
import { graphFieldOptions, HorizontalGroup, IconButton, Input, RadioButtonGroup, Tooltip } from '../..';
|
||||
import { StackingConfig, StackingMode } from '@grafana/schema';
|
||||
|
||||
export const StackingEditor: React.FC<FieldOverrideEditorProps<StackingConfig, any>> = ({
|
||||
value,
|
||||
|
||||
+1
-1
@@ -385,7 +385,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
})
|
||||
|
||||
apiRoute.Get("/alert-notifiers", reqEditorRole, routing.Wrap(
|
||||
GetAlertNotifiers(hs.MultiOrgAlertmanager != nil && hs.Cfg.IsNgAlertEnabled())),
|
||||
GetAlertNotifiers(hs.Cfg.IsNgAlertEnabled())),
|
||||
)
|
||||
|
||||
apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
|
||||
|
||||
+2
-29
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
@@ -229,33 +229,6 @@ func (s *fakeRenderService) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ accesscontrol.AccessControl = new(fakeAccessControl)
|
||||
|
||||
type fakeAccessControl struct {
|
||||
isDisabled bool
|
||||
permissions []*accesscontrol.Permission
|
||||
}
|
||||
|
||||
func (f *fakeAccessControl) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
permissions, err := f.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions))
|
||||
}
|
||||
|
||||
func (f *fakeAccessControl) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
||||
return f.permissions, nil
|
||||
}
|
||||
|
||||
func (f *fakeAccessControl) IsDisabled() bool {
|
||||
return f.isDisabled
|
||||
}
|
||||
|
||||
func (f *fakeAccessControl) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []*accesscontrol.Permission) (*scenarioContext, *HTTPServer) {
|
||||
cfg.FeatureToggles = make(map[string]bool)
|
||||
cfg.FeatureToggles["accesscontrol"] = true
|
||||
@@ -263,7 +236,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
|
||||
hs := &HTTPServer{
|
||||
Cfg: cfg,
|
||||
RouteRegister: routing.NewRouteRegister(),
|
||||
AccessControl: &fakeAccessControl{permissions: permissions},
|
||||
AccessControl: accesscontrolmock.New().WithPermissions(permissions),
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
|
||||
@@ -42,7 +42,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/live"
|
||||
"github.com/grafana/grafana/pkg/services/live/pushhttp"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
@@ -104,7 +103,6 @@ type HTTPServer struct {
|
||||
PluginDashboardService *plugindashboards.Service `inject:""`
|
||||
AlertEngine *alerting.AlertEngine `inject:""`
|
||||
LoadSchemaService *schemaloader.SchemaLoaderService `inject:""`
|
||||
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager `inject:""`
|
||||
LibraryPanelService librarypanels.Service `inject:""`
|
||||
LibraryElementService libraryelements.Service `inject:""`
|
||||
SocialService social.Service `inject:""`
|
||||
|
||||
@@ -376,12 +376,6 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)) {
|
||||
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
|
||||
Text: "Stats", Id: "server-stats", Url: hs.Cfg.AppSubURL + "/admin/stats", Icon: "graph-bar",
|
||||
})
|
||||
}
|
||||
|
||||
if hs.Cfg.LDAPEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) {
|
||||
adminNavLinks = append(adminNavLinks, &dtos.NavLink{
|
||||
Text: "LDAP", Id: "ldap", Url: hs.Cfg.AppSubURL + "/admin/ldap", Icon: "book",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
)
|
||||
|
||||
type middlewareTestCase struct {
|
||||
@@ -27,26 +27,24 @@ func TestMiddleware(t *testing.T) {
|
||||
tests := []middlewareTestCase{
|
||||
{
|
||||
desc: "should use fallback if access control is disabled",
|
||||
ac: fakeAccessControl{isDisabled: true},
|
||||
ac: mock.New().WithDisabled(),
|
||||
expectFallback: true,
|
||||
expectEndpoint: true,
|
||||
},
|
||||
{
|
||||
desc: "should pass middleware for correct permissions",
|
||||
ac: fakeAccessControl{
|
||||
isDisabled: false,
|
||||
permissions: []*accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
},
|
||||
ac: mock.New().WithPermissions(
|
||||
[]*accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
),
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
expectFallback: false,
|
||||
expectEndpoint: true,
|
||||
},
|
||||
{
|
||||
desc: "should not reach endpoint when missing permissions",
|
||||
ac: fakeAccessControl{
|
||||
isDisabled: false,
|
||||
permissions: []*accesscontrol.Permission{{Action: "users:read", Scope: "users:1"}},
|
||||
},
|
||||
ac: mock.New().WithPermissions(
|
||||
[]*accesscontrol.Permission{{Action: "users:read", Scope: "users:1"}},
|
||||
),
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
expectFallback: false,
|
||||
expectEndpoint: false,
|
||||
@@ -95,27 +93,3 @@ func contextProvider() macaron.Handler {
|
||||
c.Map(reqCtx)
|
||||
}
|
||||
}
|
||||
|
||||
var _ accesscontrol.AccessControl = new(fakeAccessControl)
|
||||
|
||||
type fakeAccessControl struct {
|
||||
isDisabled bool
|
||||
permissions []*accesscontrol.Permission
|
||||
}
|
||||
|
||||
func (f fakeAccessControl) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
permissions, _ := f.GetUserPermissions(ctx, user)
|
||||
return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions))
|
||||
}
|
||||
|
||||
func (f fakeAccessControl) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
||||
return f.permissions, nil
|
||||
}
|
||||
|
||||
func (f fakeAccessControl) IsDisabled() bool {
|
||||
return f.isDisabled
|
||||
}
|
||||
|
||||
func (f fakeAccessControl) DeclareFixedRoles(registration ...accesscontrol.RoleRegistration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
)
|
||||
|
||||
type fullAccessControl interface {
|
||||
accesscontrol.AccessControl
|
||||
GetUserBuiltInRoles(user *models.SignedInUser) []string
|
||||
RegisterFixedRoles() error
|
||||
}
|
||||
|
||||
type Calls struct {
|
||||
Evaluate []interface{}
|
||||
GetUserPermissions []interface{}
|
||||
IsDisabled []interface{}
|
||||
DeclareFixedRoles []interface{}
|
||||
GetUserBuiltInRoles []interface{}
|
||||
RegisterFixedRoles []interface{}
|
||||
}
|
||||
|
||||
type Mock struct {
|
||||
// Unless an override is provided, permissions will be returned by GetUserPermissions
|
||||
permissions []*accesscontrol.Permission
|
||||
// Unless an override is provided, disabled will be returned by IsDisabled
|
||||
disabled bool
|
||||
// Unless an override is provided, builtInRoles will be returned by GetUserBuiltInRoles
|
||||
builtInRoles []string
|
||||
|
||||
// Track the list of calls
|
||||
Calls Calls
|
||||
|
||||
// Override functions
|
||||
EvaluateFunc func(context.Context, *models.SignedInUser, accesscontrol.Evaluator) (bool, error)
|
||||
GetUserPermissionsFunc func(context.Context, *models.SignedInUser) ([]*accesscontrol.Permission, error)
|
||||
IsDisabledFunc func() bool
|
||||
DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error
|
||||
GetUserBuiltInRolesFunc func(user *models.SignedInUser) []string
|
||||
RegisterFixedRolesFunc func() error
|
||||
}
|
||||
|
||||
type MockOptions func(*Mock)
|
||||
|
||||
// Ensure the mock stays in line with the interface
|
||||
var _ fullAccessControl = New()
|
||||
|
||||
func New() *Mock {
|
||||
mock := &Mock{
|
||||
Calls: Calls{},
|
||||
disabled: false,
|
||||
permissions: []*accesscontrol.Permission{},
|
||||
builtInRoles: []string{},
|
||||
}
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
func (m Mock) WithPermissions(permissions []*accesscontrol.Permission) *Mock {
|
||||
m.permissions = permissions
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m Mock) WithDisabled() *Mock {
|
||||
m.disabled = true
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m Mock) WithBuiltInRoles(builtInRoles []string) *Mock {
|
||||
m.builtInRoles = builtInRoles
|
||||
return &m
|
||||
}
|
||||
|
||||
// Evaluate evaluates access to the given resource.
|
||||
// This mock uses GetUserPermissions to then call the evaluator Evaluate function.
|
||||
func (m *Mock) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, user, evaluator})
|
||||
// Use override if provided
|
||||
if m.EvaluateFunc != nil {
|
||||
return m.EvaluateFunc(ctx, user, evaluator)
|
||||
}
|
||||
// Otherwise perform an actual evaluation of the permissions
|
||||
permissions, err := m.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions))
|
||||
}
|
||||
|
||||
// GetUserPermissions returns user permissions.
|
||||
// This mock return m.permissions unless an override is provided.
|
||||
func (m *Mock) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
||||
m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user})
|
||||
// Use override if provided
|
||||
if m.GetUserPermissionsFunc != nil {
|
||||
return m.GetUserPermissionsFunc(ctx, user)
|
||||
}
|
||||
// Otherwise return the Permissions list
|
||||
return m.permissions, nil
|
||||
}
|
||||
|
||||
// Middleware checks if service disabled or not to switch to fallback authorization.
|
||||
// This mock return m.disabled unless an override is provided.
|
||||
func (m *Mock) IsDisabled() bool {
|
||||
m.Calls.IsDisabled = append(m.Calls.IsDisabled, struct{}{})
|
||||
// Use override if provided
|
||||
if m.IsDisabledFunc != nil {
|
||||
return m.IsDisabledFunc()
|
||||
}
|
||||
// Otherwise return the Disabled bool
|
||||
return m.disabled
|
||||
}
|
||||
|
||||
// DeclareFixedRoles allow the caller to declare, to the service, fixed roles and their
|
||||
// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
|
||||
// This mock returns no error unless an override is provided.
|
||||
func (m *Mock) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error {
|
||||
m.Calls.DeclareFixedRoles = append(m.Calls.DeclareFixedRoles, []interface{}{registrations})
|
||||
// Use override if provided
|
||||
if m.DeclareFixedRolesFunc != nil {
|
||||
return m.DeclareFixedRolesFunc(registrations...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserBuiltInRoles returns the list of organizational roles ("Viewer", "Editor", "Admin")
|
||||
// or "Grafana Admin" associated to a user
|
||||
// This mock returns m.builtInRoles unless an override is provided.
|
||||
func (m *Mock) GetUserBuiltInRoles(user *models.SignedInUser) []string {
|
||||
m.Calls.GetUserBuiltInRoles = append(m.Calls.GetUserBuiltInRoles, []interface{}{user})
|
||||
|
||||
// Use override if provided
|
||||
if m.GetUserBuiltInRolesFunc != nil {
|
||||
return m.GetUserBuiltInRolesFunc(user)
|
||||
}
|
||||
|
||||
// Otherwise return the BuiltInRoles list
|
||||
return m.builtInRoles
|
||||
}
|
||||
|
||||
// RegisterFixedRoles registers all roles declared to AccessControl
|
||||
// This mock returns no error unless an override is provided.
|
||||
func (m *Mock) RegisterFixedRoles() error {
|
||||
m.Calls.RegisterFixedRoles = append(m.Calls.RegisterFixedRoles, []struct{}{})
|
||||
// Use override if provided
|
||||
if m.RegisterFixedRolesFunc != nil {
|
||||
return m.RegisterFixedRolesFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (l *OSSLicensingService) Init() error {
|
||||
for _, node := range indexData.NavTree {
|
||||
if node.Id == "admin" {
|
||||
node.Children = append(node.Children, &dtos.NavLink{
|
||||
Text: "Upgrade",
|
||||
Text: "Stats and license",
|
||||
Id: "upgrading",
|
||||
Url: l.LicenseURL(req.SignedInUser),
|
||||
Icon: "unlock",
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
@@ -126,6 +127,12 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
for _, next := range m {
|
||||
v.Receivers = append(v.Receivers, next)
|
||||
}
|
||||
|
||||
// Make sure the return order is deterministic.
|
||||
sort.Slice(v.Receivers, func(i, j int) bool {
|
||||
return v.Receivers[i].Name < v.Receivers[j].Name
|
||||
})
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { size } from 'lodash';
|
||||
import { ansicolor, BarAlignment, colors, DrawStyle, StackingMode } from '@grafana/ui';
|
||||
import { BarAlignment, DrawStyle, StackingMode } from '@grafana/schema';
|
||||
import { ansicolor, colors } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import renderer from 'react-test-renderer';
|
||||
import { ServerStats } from './ServerStats';
|
||||
import { createNavModel } from 'test/mocks/common';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ServerStats, Props } from './ServerStats';
|
||||
import { ServerStat } from './state/apis';
|
||||
|
||||
const stats: ServerStat = {
|
||||
activeAdmins: 1,
|
||||
activeEditors: 0,
|
||||
activeSessions: 1,
|
||||
activeUsers: 1,
|
||||
activeViewers: 0,
|
||||
admins: 1,
|
||||
alerts: 5,
|
||||
dashboards: 1599,
|
||||
datasources: 54,
|
||||
editors: 2,
|
||||
orgs: 1,
|
||||
playlists: 1,
|
||||
snapshots: 1,
|
||||
stars: 3,
|
||||
tags: 42,
|
||||
users: 5,
|
||||
viewers: 2,
|
||||
};
|
||||
|
||||
const getServerStats = () => {
|
||||
return Promise.resolve(stats);
|
||||
};
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>) => {
|
||||
const props: Props = {
|
||||
getServerStats,
|
||||
};
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
render(<ServerStats {...props} />);
|
||||
};
|
||||
describe('ServerStats', () => {
|
||||
it('Should render table with stats', (done) => {
|
||||
const navModel = createNavModel('Admin', 'stats');
|
||||
const stats: ServerStat[] = [
|
||||
{ name: 'Total dashboards', value: 10 },
|
||||
{ name: 'Total Users', value: 1 },
|
||||
];
|
||||
|
||||
const getServerStats = () => {
|
||||
return Promise.resolve(stats);
|
||||
};
|
||||
|
||||
const page = renderer.create(<ServerStats navModel={navModel} getServerStats={getServerStats} />);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(page.toJSON()).toBeDefined();
|
||||
done();
|
||||
});
|
||||
it('Should render page with stats', async () => {
|
||||
setup();
|
||||
expect(await screen.findByRole('heading', { name: /instance statistics/i })).toBeInTheDocument();
|
||||
expect(screen.getByText('Dashboards (starred)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tags')).toBeInTheDocument();
|
||||
expect(screen.getByText('Playlists')).toBeInTheDocument();
|
||||
expect(screen.getByText('Snapshots')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'Manage dashboards' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,79 +1,187 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import { Icon, Tooltip } from '@grafana/ui';
|
||||
import { NavModel } from '@grafana/data';
|
||||
import { StoreState } from 'app/types';
|
||||
import { css } from '@emotion/css';
|
||||
import { CardContainer, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { AccessControlAction, StoreState } from 'app/types';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { getServerStats, ServerStat } from './state/apis';
|
||||
import { contextSrv } from '../../core/services/context_srv';
|
||||
import { Loader } from '../plugins/admin/components/Loader';
|
||||
|
||||
interface Props {
|
||||
navModel: NavModel;
|
||||
getServerStats: () => Promise<ServerStat[]>;
|
||||
export interface Props {
|
||||
getServerStats: () => Promise<ServerStat | null>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
stats: ServerStat[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
export const ServerStats = ({ getServerStats }: Props) => {
|
||||
const [stats, setStats] = useState<ServerStat | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
export class ServerStats extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
stats: [],
|
||||
isLoading: true,
|
||||
};
|
||||
useEffect(() => {
|
||||
getServerStats().then((stats) => {
|
||||
setStats(stats);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [getServerStats]);
|
||||
|
||||
async componentDidMount() {
|
||||
try {
|
||||
const stats = await this.props.getServerStats();
|
||||
this.setState({ stats, isLoading: false });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (!contextSrv.hasPermission(AccessControlAction.ActionServerStatsRead)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel } = this.props;
|
||||
const { stats, isLoading } = this.state;
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents isLoading={isLoading}>
|
||||
<table className="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{stats.map(StatItem)}</tbody>
|
||||
</table>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function StatItem(stat: ServerStat) {
|
||||
return (
|
||||
<tr key={stat.name}>
|
||||
<td>
|
||||
{stat.name}{' '}
|
||||
{stat.tooltip && (
|
||||
<Tooltip content={stat.tooltip} placement={'top'}>
|
||||
<Icon name={'info-circle'} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
<td>{stat.value}</td>
|
||||
</tr>
|
||||
<>
|
||||
<h2 className={styles.title}>Instance statistics</h2>
|
||||
{isLoading ? (
|
||||
<div className={styles.loader}>
|
||||
<Loader text={'Loading instance stats...'} />
|
||||
</div>
|
||||
) : stats ? (
|
||||
<div className={styles.row}>
|
||||
<StatCard
|
||||
content={[
|
||||
{ name: 'Dashboards (starred)', value: `${stats.dashboards} (${stats.stars})` },
|
||||
{ name: 'Tags', value: stats.tags },
|
||||
{ name: 'Playlists', value: stats.playlists },
|
||||
{ name: 'Snapshots', value: stats.snapshots },
|
||||
]}
|
||||
footer={
|
||||
<LinkButton href={'/dashboards'} variant={'secondary'}>
|
||||
Manage dashboards
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={styles.doubleRow}>
|
||||
<StatCard
|
||||
content={[{ name: 'Data sources', value: stats.datasources }]}
|
||||
footer={
|
||||
<LinkButton href={'/datasources'} variant={'secondary'}>
|
||||
Manage data sources
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
<StatCard
|
||||
content={[{ name: 'Alerts', value: stats.alerts }]}
|
||||
footer={
|
||||
<LinkButton href={'/alerting/list'} variant={'secondary'}>
|
||||
Alerts
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<StatCard
|
||||
content={[
|
||||
{ name: 'Organisations', value: stats.orgs },
|
||||
{ name: 'Users total', value: stats.users },
|
||||
{ name: 'Active users in last 30 days', value: stats.activeUsers },
|
||||
{ name: 'Active sessions', value: stats.activeSessions },
|
||||
]}
|
||||
footer={
|
||||
<LinkButton href={'/admin/users'} variant={'secondary'}>
|
||||
Manage users
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className={styles.notFound}>No stats found.</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
title: css`
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
`,
|
||||
row: css`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
& > div:not(:last-of-type) {
|
||||
margin-right: ${theme.spacing(2)};
|
||||
}
|
||||
|
||||
& > div {
|
||||
width: 33.3%;
|
||||
}
|
||||
`,
|
||||
doubleRow: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div:first-of-type {
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
}
|
||||
`,
|
||||
|
||||
loader: css`
|
||||
height: 290px;
|
||||
`,
|
||||
|
||||
notFound: css`
|
||||
font-size: ${theme.typography.h6.fontSize};
|
||||
text-align: center;
|
||||
height: 290px;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
navModel: getNavModel(state.navIndex, 'server-stats'),
|
||||
getServerStats: getServerStats,
|
||||
getServerStats,
|
||||
});
|
||||
|
||||
type StatCardProps = {
|
||||
content: Array<Record<string, number | string>>;
|
||||
footer?: JSX.Element;
|
||||
};
|
||||
|
||||
const StatCard = ({ content, footer }: StatCardProps) => {
|
||||
const styles = useStyles2(getCardStyles);
|
||||
return (
|
||||
<CardContainer className={styles.container} disableHover>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.content}>
|
||||
{content.map((item) => {
|
||||
return (
|
||||
<div key={item.name} className={styles.row}>
|
||||
<span>{item.name}</span>
|
||||
<span>{item.value}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{footer && <div>{footer}</div>}
|
||||
</div>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getCardStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
container: css`
|
||||
padding: ${theme.spacing(2)};
|
||||
`,
|
||||
inner: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`,
|
||||
content: css`
|
||||
flex: 1 0 auto;
|
||||
`,
|
||||
row: css`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
align-items: center;
|
||||
`,
|
||||
};
|
||||
};
|
||||
export default hot(module)(connect(mapStateToProps)(ServerStats));
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { NavModel } from '@grafana/data';
|
||||
import Page from '../../core/components/Page/Page';
|
||||
import { LicenseChrome } from './LicenseChrome';
|
||||
import { LinkButton } from '@grafana/ui';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { StoreState } from '../../types';
|
||||
import { getNavModel } from '../../core/selectors/navModel';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { css } from '@emotion/css';
|
||||
import { LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2, NavModel } from '@grafana/data';
|
||||
import Page from '../../core/components/Page/Page';
|
||||
import { getNavModel } from '../../core/selectors/navModel';
|
||||
import { LicenseChrome } from './LicenseChrome';
|
||||
import { StoreState } from '../../types';
|
||||
import ServerStats from './ServerStats';
|
||||
|
||||
interface Props {
|
||||
navModel: NavModel;
|
||||
@@ -17,6 +18,7 @@ export const UpgradePage: React.FC<Props> = ({ navModel }) => {
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<ServerStats />
|
||||
<UpgradeInfo
|
||||
editionNotice="You are running the open-source version of Grafana.
|
||||
You have to install the Enterprise edition in order enable Enterprise features."
|
||||
@@ -33,27 +35,39 @@ interface UpgradeInfoProps {
|
||||
}
|
||||
|
||||
export const UpgradeInfo: React.FC<UpgradeInfoProps> = ({ editionNotice }) => {
|
||||
const columnStyles = css`
|
||||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
column-gap: 20px;
|
||||
row-gap: 40px;
|
||||
|
||||
@media (min-width: 1050px) {
|
||||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
`;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<LicenseChrome header="Grafana Enterprise" subheader="Get your free trial" editionNotice={editionNotice}>
|
||||
<div className={columnStyles}>
|
||||
<FeatureInfo />
|
||||
<ServiceInfo />
|
||||
</div>
|
||||
</LicenseChrome>
|
||||
<>
|
||||
<h2 className={styles.title}>Enterprise license</h2>
|
||||
<LicenseChrome header="Grafana Enterprise" subheader="Get your free trial" editionNotice={editionNotice}>
|
||||
<div className={styles.column}>
|
||||
<FeatureInfo />
|
||||
<ServiceInfo />
|
||||
</div>
|
||||
</LicenseChrome>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
column: css`
|
||||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
column-gap: 20px;
|
||||
row-gap: 40px;
|
||||
|
||||
@media (min-width: 1050px) {
|
||||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
`,
|
||||
title: css`
|
||||
margin: ${theme.spacing(4)} 0;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
const GetEnterprise: React.FC = () => {
|
||||
return (
|
||||
<div style={{ marginTop: '40px', marginBottom: '30px' }}>
|
||||
|
||||
@@ -1,58 +1,30 @@
|
||||
import React from 'react';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { PopoverContent } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
export interface ServerStat {
|
||||
name: string;
|
||||
value: number;
|
||||
tooltip?: PopoverContent;
|
||||
activeAdmins: number;
|
||||
activeEditors: number;
|
||||
activeSessions: number;
|
||||
activeUsers: number;
|
||||
activeViewers: number;
|
||||
admins: number;
|
||||
alerts: number;
|
||||
dashboards: number;
|
||||
datasources: number;
|
||||
editors: number;
|
||||
orgs: number;
|
||||
playlists: number;
|
||||
snapshots: number;
|
||||
stars: number;
|
||||
tags: number;
|
||||
users: number;
|
||||
viewers: number;
|
||||
}
|
||||
|
||||
const { hasLicense } = config.licenseInfo;
|
||||
|
||||
export const getServerStats = async (): Promise<ServerStat[]> => {
|
||||
try {
|
||||
const res = await getBackendSrv().get('api/admin/stats');
|
||||
return [
|
||||
{ name: 'Total users', value: res.users },
|
||||
...(!hasLicense
|
||||
? [
|
||||
{ name: 'Total admins', value: res.admins },
|
||||
{ name: 'Total editors', value: res.editors },
|
||||
{ name: 'Total viewers', value: res.viewers },
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'Active users (seen last 30 days)',
|
||||
value: res.activeUsers,
|
||||
tooltip: hasLicense
|
||||
? () => (
|
||||
<>
|
||||
For active user count by role, see the <a href="/admin/licensing">Licensing page</a>.
|
||||
</>
|
||||
)
|
||||
: '',
|
||||
},
|
||||
...(!hasLicense
|
||||
? [
|
||||
{ name: 'Active admins (seen last 30 days)', value: res.activeAdmins },
|
||||
{ name: 'Active editors (seen last 30 days)', value: res.activeEditors },
|
||||
{ name: 'Active viewers (seen last 30 days)', value: res.activeViewers },
|
||||
]
|
||||
: []),
|
||||
{ name: 'Active sessions', value: res.activeSessions },
|
||||
{ name: 'Total dashboards', value: res.dashboards },
|
||||
{ name: 'Total orgs', value: res.orgs },
|
||||
{ name: 'Total playlists', value: res.playlists },
|
||||
{ name: 'Total snapshots', value: res.snapshots },
|
||||
{ name: 'Total dashboard tags', value: res.tags },
|
||||
{ name: 'Total starred dashboards', value: res.stars },
|
||||
{ name: 'Total alerts', value: res.alerts },
|
||||
{ name: 'Total data sources', value: res.datasources },
|
||||
];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
export const getServerStats = async (): Promise<ServerStat | null> => {
|
||||
return getBackendSrv()
|
||||
.get('api/admin/stats')
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
DrawStyle,
|
||||
Icon,
|
||||
PanelContext,
|
||||
PanelContextProvider,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
useTheme2,
|
||||
ZoomPlugin,
|
||||
} from '@grafana/ui';
|
||||
import { LegendDisplayMode, TooltipDisplayMode } from '@grafana/schema';
|
||||
import { LegendDisplayMode, TooltipDisplayMode, DrawStyle } from '@grafana/schema';
|
||||
import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config';
|
||||
import { ContextMenuPlugin } from 'app/plugins/panel/timeseries/plugins/ContextMenuPlugin';
|
||||
import { ExemplarsPlugin } from 'app/plugins/panel/timeseries/plugins/ExemplarsPlugin';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DrawStyle, StackingMode } from '@grafana/ui';
|
||||
import { DrawStyle, StackingMode } from '@grafana/schema';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
ArrayVector,
|
||||
|
||||
@@ -2,11 +2,15 @@ import React from 'react';
|
||||
import { LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Page } from './Page';
|
||||
|
||||
export const Loader = () => {
|
||||
export interface Props {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export const Loader = ({ text = 'Loading...' }: Props) => {
|
||||
return (
|
||||
<Page>
|
||||
<div className="page-loader-wrapper">
|
||||
<LoadingPlaceholder text="Loading..." />
|
||||
<LoadingPlaceholder text={text} />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import { TooltipDisplayMode, StackingMode } from '@grafana/schema';
|
||||
import { PanelProps, TimeRange, VizOrientation } from '@grafana/data';
|
||||
import { StackingMode, TooltipPlugin, useTheme2 } from '@grafana/ui';
|
||||
import { TooltipPlugin, useTheme2 } from '@grafana/ui';
|
||||
import { BarChartOptions } from './types';
|
||||
import { BarChart } from './BarChart';
|
||||
import { prepareGraphableFrames } from './utils';
|
||||
|
||||
@@ -2,15 +2,14 @@ import uPlot, { Axis, AlignedData } from 'uplot';
|
||||
import { pointWithin, Quadtree, Rect } from './quadtree';
|
||||
import { distribute, SPACE_BETWEEN } from './distribute';
|
||||
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
|
||||
import { VizTextDisplayOptions } from '@grafana/schema';
|
||||
import { calculateFontSize, PlotTooltipInterpolator } from '@grafana/ui';
|
||||
import {
|
||||
calculateFontSize,
|
||||
PlotTooltipInterpolator,
|
||||
StackingMode,
|
||||
BarValueVisibility,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/ui';
|
||||
VizTextDisplayOptions,
|
||||
} from '@grafana/schema';
|
||||
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
||||
|
||||
const groupDistr = SPACE_BETWEEN;
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
VizOrientation,
|
||||
} from '@grafana/data';
|
||||
import { BarChartPanel } from './BarChartPanel';
|
||||
import { StackingMode, BarValueVisibility, graphFieldOptions, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { StackingMode, BarValueVisibility } from '@grafana/schema';
|
||||
import { graphFieldOptions, commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
import { BarChartFieldConfig, BarChartOptions, defaultBarChartFieldConfig } from 'app/plugins/panel/barchart/types';
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { OptionsWithLegend, OptionsWithTextFormatting, OptionsWithTooltip } from '@grafana/schema';
|
||||
import {
|
||||
OptionsWithLegend,
|
||||
OptionsWithTextFormatting,
|
||||
OptionsWithTooltip,
|
||||
AxisConfig,
|
||||
BarValueVisibility,
|
||||
GraphGradientMode,
|
||||
HideableFieldConfig,
|
||||
StackingMode,
|
||||
} from '@grafana/schema';
|
||||
import { VizOrientation } from '@grafana/data';
|
||||
import { AxisConfig, BarValueVisibility, GraphGradientMode, HideableFieldConfig, StackingMode } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { prepareGraphableFrames, preparePlotConfigBuilder, preparePlotFrame } from './utils';
|
||||
import { LegendDisplayMode, TooltipDisplayMode } from '@grafana/schema';
|
||||
import {
|
||||
LegendDisplayMode,
|
||||
TooltipDisplayMode,
|
||||
BarValueVisibility,
|
||||
GraphGradientMode,
|
||||
StackingMode,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
createTheme,
|
||||
DefaultTimeZone,
|
||||
@@ -11,7 +17,6 @@ import {
|
||||
VizOrientation,
|
||||
} from '@grafana/data';
|
||||
import { BarChartFieldConfig, BarChartOptions } from './types';
|
||||
import { BarValueVisibility, GraphGradientMode, StackingMode } from '@grafana/ui';
|
||||
|
||||
function mockDataFrame() {
|
||||
const df1 = new MutableDataFrame({
|
||||
|
||||
@@ -13,16 +13,8 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { BarChartFieldConfig, BarChartOptions, defaultBarChartFieldConfig } from './types';
|
||||
import { BarsOptions, getConfig } from './bars';
|
||||
import {
|
||||
AxisPlacement,
|
||||
FIXED_UNIT,
|
||||
ScaleDirection,
|
||||
ScaleDistribution,
|
||||
ScaleOrientation,
|
||||
StackingMode,
|
||||
UPlotConfigBuilder,
|
||||
UPlotConfigPrepFn,
|
||||
} from '@grafana/ui';
|
||||
import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation, StackingMode } from '@grafana/schema';
|
||||
import { FIXED_UNIT, UPlotConfigBuilder, UPlotConfigPrepFn } from '@grafana/ui';
|
||||
import { collectStackingGroups } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
||||
|
||||
/** @alpha */
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import React from 'react';
|
||||
import uPlot, { AlignedData } from 'uplot';
|
||||
import { VizLegendOptions, LegendDisplayMode } from '@grafana/schema';
|
||||
import {
|
||||
VizLegendOptions,
|
||||
LegendDisplayMode,
|
||||
ScaleDistribution,
|
||||
AxisPlacement,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
DataFrame,
|
||||
formattedValueToString,
|
||||
@@ -8,23 +15,13 @@ import {
|
||||
getFieldSeriesColor,
|
||||
GrafanaTheme2,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
Themeable2,
|
||||
UPlotConfigBuilder,
|
||||
UPlotChart,
|
||||
VizLayout,
|
||||
AxisPlacement,
|
||||
ScaleDirection,
|
||||
ScaleOrientation,
|
||||
PlotLegend,
|
||||
} from '@grafana/ui';
|
||||
import { Themeable2, UPlotConfigBuilder, UPlotChart, VizLayout, PlotLegend } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
histogramBucketSizes,
|
||||
histogramFrameBucketMaxFieldName,
|
||||
} from '@grafana/data/src/transformations/transformers/histogram';
|
||||
import { PanelOptions } from './models.gen';
|
||||
import { ScaleDistribution } from '@grafana/ui/src/components/uPlot/models.gen';
|
||||
|
||||
function incrRoundDn(num: number, incr: number) {
|
||||
return Math.floor(num / incr) * incr;
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
// It is currenty hand written but will serve as the target for cuetsy
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
import { LegendDisplayMode, OptionsWithLegend, OptionsWithTooltip, TooltipDisplayMode } from '@grafana/schema';
|
||||
import { GraphGradientMode, HideableFieldConfig } from '@grafana/ui';
|
||||
import {
|
||||
LegendDisplayMode,
|
||||
OptionsWithLegend,
|
||||
OptionsWithTooltip,
|
||||
TooltipDisplayMode,
|
||||
GraphGradientMode,
|
||||
HideableFieldConfig,
|
||||
} from '@grafana/schema';
|
||||
|
||||
export const modelVersion = Object.freeze([1, 0]);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { LegendDisplayMode } from '@grafana/schema';
|
||||
import { LegendDisplayMode, BarValueVisibility } from '@grafana/schema';
|
||||
import {
|
||||
PanelContext,
|
||||
PanelContextRoot,
|
||||
GraphNG,
|
||||
GraphNGProps,
|
||||
BarValueVisibility,
|
||||
UPlotConfigBuilder,
|
||||
VizLayout,
|
||||
VizLegend,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { StateTimelinePanel } from './StateTimelinePanel';
|
||||
import { TimelineOptions, TimelineFieldConfig, defaultPanelOptions, defaultTimelineFieldConfig } from './types';
|
||||
import { BarValueVisibility, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { BarValueVisibility } from '@grafana/schema';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
import { timelinePanelChangedHandler } from './migrations';
|
||||
|
||||
export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(StateTimelinePanel)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pointWithin, Quadtree, Rect } from 'app/plugins/panel/barchart/quadtree
|
||||
import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute';
|
||||
import { TimelineFieldConfig, TimelineMode, TimelineValueAlignment } from './types';
|
||||
import { GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import { BarValueVisibility } from '@grafana/schema';
|
||||
import { alpha } from '@grafana/data/src/themes/colorManipulator';
|
||||
|
||||
const { round, min, ceil } = Math;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OptionsWithTooltip, OptionsWithLegend } from '@grafana/schema';
|
||||
import { HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
import { OptionsWithTooltip, OptionsWithLegend, HideableFieldConfig, BarValueVisibility } from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { VizLegendOptions } from '@grafana/schema';
|
||||
import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types';
|
||||
import {
|
||||
ArrayVector,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
VizLegendItem,
|
||||
} from '@grafana/ui';
|
||||
import { getConfig, TimelineCoreOptions } from './timeline';
|
||||
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
|
||||
import { VizLegendOptions, AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema';
|
||||
import { TimelineFieldConfig, TimelineOptions } from './types';
|
||||
import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
|
||||
import { preparePlotData } from '../../../../../packages/grafana-ui/src/components/uPlot/utils';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { StatusHistoryPanel } from './StatusHistoryPanel';
|
||||
import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types';
|
||||
import { BarValueVisibility, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { BarValueVisibility } from '@grafana/schema';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(StatusHistoryPanel)
|
||||
.useFieldConfig({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
import { OptionsWithTooltip, OptionsWithLegend } from '@grafana/schema';
|
||||
import { HideableFieldConfig, BarValueVisibility, OptionsWithTooltip, OptionsWithLegend } from '@grafana/schema';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { LineStyle } from '@grafana/schema';
|
||||
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { HorizontalGroup, IconButton, LineStyle, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
import { HorizontalGroup, IconButton, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
|
||||
type LineFill = 'solid' | 'dash' | 'dot';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { GraphTresholdsStyleMode } from '@grafana/schema';
|
||||
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { GraphTresholdsStyleMode, Select } from '@grafana/ui';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
export const ThresholdsStyleEditor: React.FC<
|
||||
FieldOverrideEditorProps<SelectableValue<{ mode: GraphTresholdsStyleMode }>, any>
|
||||
|
||||
@@ -10,15 +10,16 @@ import {
|
||||
BarAlignment,
|
||||
DrawStyle,
|
||||
GraphFieldConfig,
|
||||
graphFieldOptions,
|
||||
GraphGradientMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
PointVisibility,
|
||||
StackingMode,
|
||||
commonOptionsBuilder,
|
||||
GraphTresholdsStyleMode,
|
||||
} from '@grafana/ui';
|
||||
} from '@grafana/schema';
|
||||
|
||||
import { graphFieldOptions, commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
import { LineStyleEditor } from './LineStyleEditor';
|
||||
import { FillBellowToEditor } from './FillBelowToEditor';
|
||||
import { SpanNullsEditor } from './SpanNullsEditor';
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
ThresholdsMode,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
LegendDisplayMode,
|
||||
TooltipDisplayMode,
|
||||
AxisPlacement,
|
||||
DrawStyle,
|
||||
GraphFieldConfig,
|
||||
@@ -23,8 +25,7 @@ import {
|
||||
PointVisibility,
|
||||
ScaleDistribution,
|
||||
StackingMode,
|
||||
} from '@grafana/ui';
|
||||
import { LegendDisplayMode, TooltipDisplayMode } from '@grafana/schema';
|
||||
} from '@grafana/schema';
|
||||
import { TimeSeriesOptions } from './types';
|
||||
import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash';
|
||||
import { defaultGraphConfig } from './config';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GraphFieldConfig } from '@grafana/schema';
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { GraphFieldConfig, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
import { TimeSeriesPanel } from './TimeSeriesPanel';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { TimeSeriesOptions } from './types';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
GrafanaTheme2,
|
||||
isBooleanUnit,
|
||||
} from '@grafana/data';
|
||||
import { GraphFieldConfig, LineInterpolation, StackingMode } from '@grafana/ui';
|
||||
import { GraphFieldConfig, LineInterpolation, StackingMode } from '@grafana/schema';
|
||||
|
||||
// This will return a set of frames with only graphable values included
|
||||
export function prepareGraphableFields(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GraphFieldConfig, DrawStyle } from '@grafana/schema';
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { DrawStyle, GraphFieldConfig, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { commonOptionsBuilder } from '@grafana/ui';
|
||||
import { XYChartPanel } from './XYChartPanel';
|
||||
import { Options } from './types';
|
||||
import { XYDimsEditor } from './XYDimsEditor';
|
||||
|
||||
@@ -34,4 +34,6 @@ export enum AccessControlAction {
|
||||
LDAPUsersSync = 'ldap.user:sync',
|
||||
LDAPStatusRead = 'ldap.status:read',
|
||||
DataSourcesExplore = 'datasources:explore',
|
||||
|
||||
ActionServerStatsRead = 'server.stats:read',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user