Merge branch 'main' into sdboyer/relocate-scuemata

This commit is contained in:
sam boyer
2021-08-24 15:52:14 -04:00
68 changed files with 829 additions and 737 deletions
@@ -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.
+2
View File
@@ -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';
@@ -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,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
View File
@@ -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
View File
@@ -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)
-2
View File
@@ -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:""`
-6
View File
@@ -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
}
+152
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+2 -1
View File
@@ -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,
+42 -21
View File
@@ -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();
});
});
+168 -60
View File
@@ -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));
+38 -24
View File
@@ -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' }}>
+24 -52
View File
@@ -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';
+3 -4
View File
@@ -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;
+2 -1
View File
@@ -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';
+10 -2
View File
@@ -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({
+2 -10
View File
@@ -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';
+1 -1
View File
@@ -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(
+2 -1
View File
@@ -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';
+2
View File
@@ -34,4 +34,6 @@ export enum AccessControlAction {
LDAPUsersSync = 'ldap.user:sync',
LDAPStatusRead = 'ldap.status:read',
DataSourcesExplore = 'datasources:explore',
ActionServerStatsRead = 'server.stats:read',
}