mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into react-query-editor
This commit is contained in:
@@ -38,7 +38,7 @@ Name | Description
|
|||||||
|
|
||||||
### IAM Roles
|
### IAM Roles
|
||||||
|
|
||||||
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If you grafana
|
Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If your Grafana
|
||||||
server is running on AWS you can use IAM Roles and authentication will be handled automatically.
|
server is running on AWS you can use IAM Roles and authentication will be handled automatically.
|
||||||
|
|
||||||
Checkout AWS docs on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
Checkout AWS docs on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface Props {
|
|||||||
hideTracksWhenNotNeeded?: boolean;
|
hideTracksWhenNotNeeded?: boolean;
|
||||||
scrollTop?: number;
|
scrollTop?: number;
|
||||||
setScrollTop: (value: React.MouseEvent<HTMLElement>) => void;
|
setScrollTop: (value: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
autoHeightMin?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +27,7 @@ export class CustomScrollbar extends PureComponent<Props> {
|
|||||||
hideTracksWhenNotNeeded: false,
|
hideTracksWhenNotNeeded: false,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
setScrollTop: () => {},
|
setScrollTop: () => {},
|
||||||
|
autoHeightMin: '0'
|
||||||
};
|
};
|
||||||
|
|
||||||
private ref: React.RefObject<Scrollbars>;
|
private ref: React.RefObject<Scrollbars>;
|
||||||
@@ -65,7 +67,6 @@ export class CustomScrollbar extends PureComponent<Props> {
|
|||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
|
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
|
||||||
// Before these where set to inhert but that caused problems with cut of legends in firefox
|
// Before these where set to inhert but that caused problems with cut of legends in firefox
|
||||||
autoHeightMin={'0'}
|
|
||||||
autoHeightMax={autoMaxHeight}
|
autoHeightMax={autoMaxHeight}
|
||||||
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
|
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
|
||||||
renderTrackVertical={props => <div {...props} className="track-vertical" />}
|
renderTrackVertical={props => <div {...props} className="track-vertical" />}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"height": "auto",
|
"height": "auto",
|
||||||
"maxHeight": "100%",
|
"maxHeight": "100%",
|
||||||
"minHeight": "0",
|
"minHeight": 0,
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
@@ -24,7 +24,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
"marginBottom": 0,
|
"marginBottom": 0,
|
||||||
"marginRight": 0,
|
"marginRight": 0,
|
||||||
"maxHeight": "calc(100% + 0px)",
|
"maxHeight": "calc(100% + 0px)",
|
||||||
"minHeight": "calc(0 + 0px)",
|
"minHeight": 0,
|
||||||
"overflow": "scroll",
|
"overflow": "scroll",
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
"right": undefined,
|
"right": undefined,
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { FormField, Props } from './FormField';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
label: 'Test',
|
||||||
|
labelWidth: 11,
|
||||||
|
value: 10,
|
||||||
|
onChange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
return shallow(<FormField {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const wrapper = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
25
packages/grafana-ui/src/components/FormField/FormField.tsx
Normal file
25
packages/grafana-ui/src/components/FormField/FormField.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { InputHTMLAttributes, FunctionComponent } from 'react';
|
||||||
|
import { FormLabel } from '..';
|
||||||
|
|
||||||
|
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
label: string;
|
||||||
|
labelWidth?: number;
|
||||||
|
inputWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
labelWidth: 6,
|
||||||
|
inputWidth: 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormField: FunctionComponent<Props> = ({ label, labelWidth, inputWidth, ...inputProps }) => {
|
||||||
|
return (
|
||||||
|
<div className="form-field">
|
||||||
|
<FormLabel width={labelWidth}>{label}</FormLabel>
|
||||||
|
<input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FormField.defaultProps = defaultProps;
|
||||||
|
export { FormField };
|
||||||
12
packages/grafana-ui/src/components/FormField/_FormField.scss
Normal file
12
packages/grafana-ui/src/components/FormField/_FormField.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.form-field {
|
||||||
|
margin-bottom: $gf-form-margin;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&--grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div
|
||||||
|
className="form-field"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
width={11}
|
||||||
|
>
|
||||||
|
Test
|
||||||
|
</Component>
|
||||||
|
<input
|
||||||
|
className="gf-form-input width-12"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="text"
|
||||||
|
value={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
42
packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
Normal file
42
packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { FunctionComponent, ReactNode } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Tooltip } from '..';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
htmlFor?: string;
|
||||||
|
isFocused?: boolean;
|
||||||
|
isInvalid?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormLabel: FunctionComponent<Props> = ({
|
||||||
|
children,
|
||||||
|
isFocused,
|
||||||
|
isInvalid,
|
||||||
|
className,
|
||||||
|
htmlFor,
|
||||||
|
tooltip,
|
||||||
|
width,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const classes = classNames(`gf-form-label width-${width ? width : '10'}`, className, {
|
||||||
|
'gf-form-label--is-focused': isFocused,
|
||||||
|
'gf-form-label--is-invalid': isInvalid,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className={classes} {...rest} htmlFor={htmlFor}>
|
||||||
|
{children}
|
||||||
|
{tooltip && (
|
||||||
|
<Tooltip placement="auto" content={tooltip}>
|
||||||
|
<div className="gf-form-help-icon--right-normal">
|
||||||
|
<i className="gicon gicon-question gicon--has-hover" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import React, { SFC, ReactNode } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: ReactNode;
|
|
||||||
htmlFor?: string;
|
|
||||||
className?: string;
|
|
||||||
isFocused?: boolean;
|
|
||||||
isInvalid?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
|
|
||||||
const classes = classNames('gf-form-label', className, {
|
|
||||||
'gf-form-label--is-focused': isFocused,
|
|
||||||
'gf-form-label--is-invalid': isInvalid,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label className={classes} {...rest} htmlFor={htmlFor}>
|
|
||||||
{children}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.panel-options-group__header {
|
.panel-options-group__header {
|
||||||
padding: 4px 20px;
|
padding: 4px 8px;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
background: $panel-options-group-header-bg;
|
background: $panel-options-group-header-bg;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup';
|
|||||||
import IndicatorsContainer from './IndicatorsContainer';
|
import IndicatorsContainer from './IndicatorsContainer';
|
||||||
import NoOptionsMessage from './NoOptionsMessage';
|
import NoOptionsMessage from './NoOptionsMessage';
|
||||||
import resetSelectStyles from './resetSelectStyles';
|
import resetSelectStyles from './resetSelectStyles';
|
||||||
import { CustomScrollbar } from '@grafana/ui';
|
import { CustomScrollbar } from '..';
|
||||||
|
|
||||||
export interface SelectOptionItem {
|
export interface SelectOptionItem {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ $select-input-bg-disabled: $input-bg-disabled;
|
|||||||
.gf-form-select-box__value-container {
|
.gf-form-select-box__value-container {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
vertical-align: middle;
|
||||||
> div {
|
> div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
height: 70px;
|
height: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thresholds-row:first-child > .thresholds-row-color-indicator {
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.thresholds-row:last-child > .thresholds-row-color-indicator {
|
.thresholds-row:last-child > .thresholds-row-color-indicator {
|
||||||
border-bottom-left-radius: $border-radius;
|
border-bottom-left-radius: $border-radius;
|
||||||
border-bottom-right-radius: $border-radius;
|
border-bottom-right-radius: $border-radius;
|
||||||
@@ -33,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-color-indicator {
|
.thresholds-row-color-indicator {
|
||||||
width: 20px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-input {
|
.thresholds-row-input {
|
||||||
@@ -45,18 +51,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thresholds-row-input-inner > div {
|
|
||||||
border-left: 1px solid $input-label-border-color;
|
|
||||||
border-top: 1px solid $input-label-border-color;
|
|
||||||
border-bottom: 1px solid $input-label-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thresholds-row-input-inner > *:nth-child(2) {
|
|
||||||
border-top-left-radius: $border-radius;
|
|
||||||
border-bottom-left-radius: $border-radius;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-input-inner > *:last-child {
|
.thresholds-row-input-inner > *:last-child {
|
||||||
@@ -74,9 +68,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-input-inner-value > input {
|
.thresholds-row-input-inner-value > input {
|
||||||
height: 100%;
|
height: $gf-form-input-height;
|
||||||
padding: 8px 10px;
|
padding: $input-padding-y $input-padding-x;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
border-top: 1px solid $input-label-border-color;
|
||||||
|
border-bottom: 1px solid $input-label-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-input-inner-color {
|
.thresholds-row-input-inner-color {
|
||||||
@@ -85,6 +81,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: $input-bg;
|
background-color: $input-bg;
|
||||||
|
border: 1px solid $input-label-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thresholds-row-input-inner-color-colorpicker {
|
.thresholds-row-input-inner-color-colorpicker {
|
||||||
@@ -99,8 +96,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 42px;
|
height: $gf-form-input-height;
|
||||||
|
padding: $input-padding-y $input-padding-x;
|
||||||
width: 42px;
|
width: 42px;
|
||||||
background-color: $input-label-border-color;
|
background-color: $input-label-bg;
|
||||||
|
border: 1px solid $input-label-border-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { ChangeEvent, PureComponent } from 'react';
|
||||||
import { MappingType, RangeMap, Select, ValueMap } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { MappingType, ValueMapping } from '../../types';
|
||||||
|
import { FormField, FormLabel, Select } from '..';
|
||||||
|
|
||||||
interface Props {
|
export interface Props {
|
||||||
mapping: ValueMap | RangeMap;
|
valueMapping: ValueMapping;
|
||||||
updateMapping: (mapping) => void;
|
updateValueMapping: (valueMapping: ValueMapping) => void;
|
||||||
removeMapping: () => void;
|
removeValueMapping: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
from: string;
|
from?: string;
|
||||||
id: number;
|
id: number;
|
||||||
operator: string;
|
operator: string;
|
||||||
text: string;
|
text: string;
|
||||||
to: string;
|
to?: string;
|
||||||
type: MappingType;
|
type: MappingType;
|
||||||
value: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappingOptions = [
|
const mappingOptions = [
|
||||||
@@ -25,36 +25,34 @@ const mappingOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default class MappingRow extends PureComponent<Props, State> {
|
export default class MappingRow extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = { ...props.valueMapping };
|
||||||
...props.mapping,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMappingValueChange = event => {
|
onMappingValueChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ value: event.target.value });
|
this.setState({ value: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingFromChange = event => {
|
onMappingFromChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ from: event.target.value });
|
this.setState({ from: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingToChange = event => {
|
onMappingToChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ to: event.target.value });
|
this.setState({ to: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingTextChange = event => {
|
onMappingTextChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ text: event.target.value });
|
this.setState({ text: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingTypeChange = mappingType => {
|
onMappingTypeChange = (mappingType: MappingType) => {
|
||||||
this.setState({ type: mappingType });
|
this.setState({ type: mappingType });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapping = () => {
|
updateMapping = () => {
|
||||||
this.props.updateMapping({ ...this.state });
|
this.props.updateValueMapping({ ...this.state } as ValueMapping);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderRow() {
|
renderRow() {
|
||||||
@@ -63,30 +61,28 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
if (type === MappingType.RangeToText) {
|
if (type === MappingType.RangeToText) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="gf-form">
|
<FormField
|
||||||
<Label width={4}>From</Label>
|
label="From"
|
||||||
|
labelWidth={4}
|
||||||
|
inputWidth={8}
|
||||||
|
onBlur={this.updateMapping}
|
||||||
|
onChange={this.onMappingFromChange}
|
||||||
|
value={from}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
label="To"
|
||||||
|
labelWidth={4}
|
||||||
|
inputWidth={8}
|
||||||
|
onBlur={this.updateMapping}
|
||||||
|
onChange={this.onMappingToChange}
|
||||||
|
value={to}
|
||||||
|
/>
|
||||||
|
<div className="gf-form gf-form--grow">
|
||||||
|
<FormLabel width={4}>Text</FormLabel>
|
||||||
<input
|
<input
|
||||||
className="gf-form-input width-8"
|
className="gf-form-input"
|
||||||
value={from}
|
|
||||||
onBlur={this.updateMapping}
|
onBlur={this.updateMapping}
|
||||||
onChange={this.onMappingFromChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<Label width={4}>To</Label>
|
|
||||||
<input
|
|
||||||
className="gf-form-input width-8"
|
|
||||||
value={to}
|
|
||||||
onBlur={this.updateMapping}
|
|
||||||
onChange={this.onMappingToChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<Label width={4}>Text</Label>
|
|
||||||
<input
|
|
||||||
className="gf-form-input width-10"
|
|
||||||
value={text}
|
value={text}
|
||||||
onBlur={this.updateMapping}
|
|
||||||
onChange={this.onMappingTextChange}
|
onChange={this.onMappingTextChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,17 +92,16 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="gf-form">
|
<FormField
|
||||||
<Label width={4}>Value</Label>
|
label="Value"
|
||||||
<input
|
labelWidth={4}
|
||||||
className="gf-form-input width-8"
|
onBlur={this.updateMapping}
|
||||||
onBlur={this.updateMapping}
|
onChange={this.onMappingValueChange}
|
||||||
onChange={this.onMappingValueChange}
|
value={value}
|
||||||
value={value}
|
inputWidth={8}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow">
|
||||||
<Label width={4}>Text</Label>
|
<FormLabel width={4}>Text</FormLabel>
|
||||||
<input
|
<input
|
||||||
className="gf-form-input"
|
className="gf-form-input"
|
||||||
onBlur={this.updateMapping}
|
onBlur={this.updateMapping}
|
||||||
@@ -124,7 +119,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<Label width={5}>Type</Label>
|
<FormLabel width={5}>Type</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Choose type"
|
placeholder="Choose type"
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
@@ -136,7 +131,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
{this.renderRow()}
|
{this.renderRow()}
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<button onClick={this.props.removeMapping} className="gf-form-label gf-form-label--btn">
|
<button onClick={this.props.removeValueMapping} className="gf-form-label gf-form-label--btn">
|
||||||
<i className="fa fa-times" />
|
<i className="fa fa-times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,27 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { GaugeOptions, MappingType, PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { defaultProps } from 'app/plugins/panel/gauge/GaugePanelOptions';
|
|
||||||
|
|
||||||
import ValueMappings from './ValueMappings';
|
import { ValueMappingsEditor, Props } from './ValueMappingsEditor';
|
||||||
|
import { MappingType } from '../../types/panel';
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: PanelOptionsProps<GaugeOptions> = {
|
const props: Props = {
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: {
|
valueMappings: [
|
||||||
...defaultProps.options,
|
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||||
mappings: [
|
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
],
|
||||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
const wrapper = shallow(<ValueMappings {...props} />);
|
const wrapper = shallow(<ValueMappingsEditor {...props} />);
|
||||||
|
|
||||||
const instance = wrapper.instance() as ValueMappings;
|
const instance = wrapper.instance() as ValueMappingsEditor;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instance,
|
instance,
|
||||||
@@ -40,18 +36,20 @@ describe('Render', () => {
|
|||||||
describe('On remove mapping', () => {
|
describe('On remove mapping', () => {
|
||||||
it('Should remove mapping with id 0', () => {
|
it('Should remove mapping with id 0', () => {
|
||||||
const { instance } = setup();
|
const { instance } = setup();
|
||||||
|
|
||||||
instance.onRemoveMapping(1);
|
instance.onRemoveMapping(1);
|
||||||
|
|
||||||
expect(instance.state.mappings).toEqual([
|
expect(instance.state.valueMappings).toEqual([
|
||||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove mapping with id 1', () => {
|
it('should remove mapping with id 1', () => {
|
||||||
const { instance } = setup();
|
const { instance } = setup();
|
||||||
|
|
||||||
instance.onRemoveMapping(2);
|
instance.onRemoveMapping(2);
|
||||||
|
|
||||||
expect(instance.state.mappings).toEqual([
|
expect(instance.state.valueMappings).toEqual([
|
||||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -67,7 +65,7 @@ describe('Next id to add', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should default to 1', () => {
|
it('should default to 1', () => {
|
||||||
const { instance } = setup({ options: { ...defaultProps.options } });
|
const { instance } = setup({ valueMappings: [] });
|
||||||
|
|
||||||
expect(instance.state.nextIdToAdd).toEqual(1);
|
expect(instance.state.nextIdToAdd).toEqual(1);
|
||||||
});
|
});
|
||||||
@@ -1,33 +1,39 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { GaugeOptions, PanelOptionsProps, MappingType, RangeMap, ValueMap, PanelOptionsGroup } from '@grafana/ui';
|
|
||||||
|
|
||||||
import MappingRow from './MappingRow';
|
import MappingRow from './MappingRow';
|
||||||
|
import { MappingType, ValueMapping } from '../../types/panel';
|
||||||
|
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
valueMappings: ValueMapping[];
|
||||||
|
onChange: (valueMappings: ValueMapping[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
mappings: Array<ValueMap | RangeMap>;
|
valueMappings: ValueMapping[];
|
||||||
nextIdToAdd: number;
|
nextIdToAdd: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ValueMappings extends PureComponent<PanelOptionsProps<GaugeOptions>, State> {
|
export class ValueMappingsEditor extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const mappings = props.options.mappings;
|
const mappings = props.valueMappings;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
mappings: mappings || [],
|
valueMappings: mappings,
|
||||||
nextIdToAdd: mappings.length > 0 ? this.getMaxIdFromMappings(mappings) : 1,
|
nextIdToAdd: mappings.length > 0 ? this.getMaxIdFromValueMappings(mappings) : 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxIdFromMappings(mappings) {
|
getMaxIdFromValueMappings(mappings: ValueMapping[]) {
|
||||||
return Math.max.apply(null, mappings.map(mapping => mapping.id).map(m => m)) + 1;
|
return Math.max.apply(null, mappings.map(mapping => mapping.id).map(m => m)) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping = () =>
|
addMapping = () =>
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
mappings: [
|
valueMappings: [
|
||||||
...prevState.mappings,
|
...prevState.valueMappings,
|
||||||
{
|
{
|
||||||
id: prevState.nextIdToAdd,
|
id: prevState.nextIdToAdd,
|
||||||
operator: '',
|
operator: '',
|
||||||
@@ -41,23 +47,23 @@ export default class ValueMappings extends PureComponent<PanelOptionsProps<Gauge
|
|||||||
nextIdToAdd: prevState.nextIdToAdd + 1,
|
nextIdToAdd: prevState.nextIdToAdd + 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
onRemoveMapping = id => {
|
onRemoveMapping = (id: number) => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState => ({
|
prevState => ({
|
||||||
mappings: prevState.mappings.filter(m => {
|
valueMappings: prevState.valueMappings.filter(m => {
|
||||||
return m.id !== id;
|
return m.id !== id;
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
() => {
|
() => {
|
||||||
this.props.onChange({ ...this.props.options, mappings: this.state.mappings });
|
this.props.onChange(this.state.valueMappings);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGauge = mapping => {
|
updateGauge = (mapping: ValueMapping) => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState => ({
|
prevState => ({
|
||||||
mappings: prevState.mappings.map(m => {
|
valueMappings: prevState.valueMappings.map(m => {
|
||||||
if (m.id === mapping.id) {
|
if (m.id === mapping.id) {
|
||||||
return { ...mapping };
|
return { ...mapping };
|
||||||
}
|
}
|
||||||
@@ -66,24 +72,24 @@ export default class ValueMappings extends PureComponent<PanelOptionsProps<Gauge
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
() => {
|
() => {
|
||||||
this.props.onChange({ ...this.props.options, mappings: this.state.mappings });
|
this.props.onChange(this.state.valueMappings);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { mappings } = this.state;
|
const { valueMappings } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelOptionsGroup title="Value Mappings">
|
<PanelOptionsGroup title="Value Mappings">
|
||||||
<div>
|
<div>
|
||||||
{mappings.length > 0 &&
|
{valueMappings.length > 0 &&
|
||||||
mappings.map((mapping, index) => (
|
valueMappings.map((valueMapping, index) => (
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key={`${mapping.text}-${index}`}
|
key={`${valueMapping.text}-${index}`}
|
||||||
mapping={mapping}
|
valueMapping={valueMapping}
|
||||||
updateMapping={this.updateGauge}
|
updateValueMapping={this.updateGauge}
|
||||||
removeMapping={() => this.onRemoveMapping(mapping.id)}
|
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -7,7 +7,9 @@ exports[`Render should render component 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key="Ok-0"
|
key="Ok-0"
|
||||||
mapping={
|
removeValueMapping={[Function]}
|
||||||
|
updateValueMapping={[Function]}
|
||||||
|
valueMapping={
|
||||||
Object {
|
Object {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"operator": "",
|
"operator": "",
|
||||||
@@ -16,12 +18,12 @@ exports[`Render should render component 1`] = `
|
|||||||
"value": "20",
|
"value": "20",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMapping={[Function]}
|
|
||||||
updateMapping={[Function]}
|
|
||||||
/>
|
/>
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key="Meh-1"
|
key="Meh-1"
|
||||||
mapping={
|
removeValueMapping={[Function]}
|
||||||
|
updateValueMapping={[Function]}
|
||||||
|
valueMapping={
|
||||||
Object {
|
Object {
|
||||||
"from": "21",
|
"from": "21",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@@ -31,8 +33,6 @@ exports[`Render should render component 1`] = `
|
|||||||
"type": 2,
|
"type": 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMapping={[Function]}
|
|
||||||
updateMapping={[Function]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -6,3 +6,5 @@
|
|||||||
@import 'PanelOptionsGroup/PanelOptionsGroup';
|
@import 'PanelOptionsGroup/PanelOptionsGroup';
|
||||||
@import 'PanelOptionsGrid/PanelOptionsGrid';
|
@import 'PanelOptionsGrid/PanelOptionsGrid';
|
||||||
@import 'ColorPicker/ColorPicker';
|
@import 'ColorPicker/ColorPicker';
|
||||||
|
@import 'ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
@import "FormField/FormField";
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
|||||||
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||||
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
export { FormLabel } from './FormLabel/FormLabel';
|
||||||
|
export { FormField } from './FormField/FormField';
|
||||||
|
|
||||||
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||||
export { ColorPicker } from './ColorPicker/ColorPicker';
|
export { ColorPicker } from './ColorPicker/ColorPicker';
|
||||||
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
||||||
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
||||||
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
|
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
|
||||||
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
|
|
||||||
export { Graph } from './Graph/Graph';
|
export { Graph } from './Graph/Graph';
|
||||||
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
|
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
|
||||||
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
|
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
|
||||||
|
export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { RangeMap, Threshold, ValueMap } from './panel';
|
|
||||||
|
|
||||||
export interface GaugeOptions {
|
|
||||||
baseColor: string;
|
|
||||||
decimals: number;
|
|
||||||
mappings: Array<RangeMap | ValueMap>;
|
|
||||||
maxValue: number;
|
|
||||||
minValue: number;
|
|
||||||
prefix: string;
|
|
||||||
showThresholdLabels: boolean;
|
|
||||||
showThresholdMarkers: boolean;
|
|
||||||
stat: string;
|
|
||||||
suffix: string;
|
|
||||||
thresholds: Threshold[];
|
|
||||||
unit: string;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from './series';
|
export * from './series';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './panel';
|
export * from './panel';
|
||||||
export * from './gauge';
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ interface BaseMap {
|
|||||||
type: MappingType;
|
type: MappingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ValueMapping = ValueMap | RangeMap;
|
||||||
|
|
||||||
export interface ValueMap extends BaseMap {
|
export interface ValueMap extends BaseMap {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import Transition from 'react-transition-group/Transition';
|
import Transition from 'react-transition-group/Transition';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,7 +8,7 @@ interface Props {
|
|||||||
unmountOnExit?: boolean;
|
unmountOnExit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FadeIn: SFC<Props> = props => {
|
export const FadeIn: FC<Props> = props => {
|
||||||
const defaultStyle = {
|
const defaultStyle = {
|
||||||
transition: `opacity ${props.duration}ms linear`,
|
transition: `opacity ${props.duration}ms linear`,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|||||||
50
public/app/core/components/Footer/Footer.tsx
Normal file
50
public/app/core/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
appName: string;
|
||||||
|
buildVersion: string;
|
||||||
|
buildCommit: string;
|
||||||
|
newGrafanaVersionExists: boolean;
|
||||||
|
newGrafanaVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Footer: FC<Props> = React.memo(({appName, buildVersion, buildCommit, newGrafanaVersionExists, newGrafanaVersion}) => {
|
||||||
|
return (
|
||||||
|
<footer className="footer">
|
||||||
|
<div className="text-center">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="http://docs.grafana.org" target="_blank">
|
||||||
|
<i className="fa fa-file-code-o" /> Docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://grafana.com/services/support" target="_blank">
|
||||||
|
<i className="fa fa-support" /> Support Plans
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://community.grafana.com/" target="_blank">
|
||||||
|
<i className="fa fa-comments-o" /> Community
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://grafana.com" target="_blank">{appName}</a> <span>v{buildVersion} (commit: {buildCommit})</span>
|
||||||
|
</li>
|
||||||
|
{newGrafanaVersionExists && (
|
||||||
|
<li>
|
||||||
|
<Tooltip placement="auto" content={newGrafanaVersion}>
|
||||||
|
<a href="https://grafana.com/get" target="_blank">
|
||||||
|
New version available!
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React, { SFC, ReactNode } from 'react';
|
|
||||||
import { Tooltip } from '@grafana/ui';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
tooltip?: string;
|
|
||||||
for?: string;
|
|
||||||
children: ReactNode;
|
|
||||||
width?: number;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Label: SFC<Props> = props => {
|
|
||||||
return (
|
|
||||||
<span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
|
|
||||||
<span>{props.children}</span>
|
|
||||||
{props.tooltip && (
|
|
||||||
<Tooltip placement="auto" content={props.tooltip}>
|
|
||||||
<div className="gf-form-help-icon--right-normal">
|
|
||||||
<i className="gicon gicon-question gicon--has-hover" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
|
export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ interface Props {
|
|||||||
onLayoutModeChanged: (mode: LayoutMode) => {};
|
onLayoutModeChanged: (mode: LayoutMode) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const LayoutSelector: SFC<Props> = props => {
|
const LayoutSelector: FC<Props> = props => {
|
||||||
const { mode, onLayoutModeChanged } = props;
|
const { mode, onLayoutModeChanged } = props;
|
||||||
return (
|
return (
|
||||||
<div className="layout-selector">
|
<div className="layout-selector">
|
||||||
|
|||||||
75
public/app/core/components/Page/Page.tsx
Normal file
75
public/app/core/components/Page/Page.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Libraries
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import { NavModel } from 'app/types';
|
||||||
|
import { getTitleFromNavModel } from 'app/core/selectors/navModel';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import PageHeader from '../PageHeader/PageHeader';
|
||||||
|
import Footer from '../Footer/Footer';
|
||||||
|
import PageContents from './PageContents';
|
||||||
|
import { CustomScrollbar } from '@grafana/ui';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
children: JSX.Element[] | JSX.Element;
|
||||||
|
navModel: NavModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Page extends Component<Props> {
|
||||||
|
private bodyClass = 'is-react';
|
||||||
|
private body = document.body;
|
||||||
|
static Header = PageHeader;
|
||||||
|
static Contents = PageContents;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.body.classList.add(this.bodyClass);
|
||||||
|
this.updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Props) {
|
||||||
|
if (prevProps.title !== this.props.title) {
|
||||||
|
this.updateTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.body.classList.remove(this.bodyClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTitle = () => {
|
||||||
|
const title = this.getPageTitle;
|
||||||
|
document.title = title ? title + ' - Grafana' : 'Grafana';
|
||||||
|
}
|
||||||
|
|
||||||
|
get getPageTitle () {
|
||||||
|
const { navModel } = this.props;
|
||||||
|
if (navModel) {
|
||||||
|
return getTitleFromNavModel(navModel) || undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { navModel } = this.props;
|
||||||
|
const { buildInfo } = config;
|
||||||
|
return (
|
||||||
|
<div className="page-scrollbar-wrapper">
|
||||||
|
<CustomScrollbar autoHeightMin={'100%'}>
|
||||||
|
<div className="page-scrollbar-content">
|
||||||
|
<PageHeader model={navModel} />
|
||||||
|
{this.props.children}
|
||||||
|
<Footer
|
||||||
|
appName="Grafana"
|
||||||
|
buildCommit={buildInfo.commit}
|
||||||
|
buildVersion={buildInfo.version}
|
||||||
|
newGrafanaVersion={buildInfo.latestVersion}
|
||||||
|
newGrafanaVersionExists={buildInfo.hasUpdate} />
|
||||||
|
</div>
|
||||||
|
</CustomScrollbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
26
public/app/core/components/Page/PageContents.tsx
Normal file
26
public/app/core/components/Page/PageContents.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Libraries
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import PageLoader from '../PageLoader/PageLoader';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isLoading?: boolean;
|
||||||
|
children: JSX.Element[] | JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageContents extends Component<Props> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isLoading } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container page-body">
|
||||||
|
{isLoading && <PageLoader />}
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageContents;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { FormEvent } from 'react';
|
||||||
import { NavModel, NavModelItem } from 'app/types';
|
import { NavModel, NavModelItem } from 'app/types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
@@ -12,8 +12,8 @@ const SelectNav = ({ main, customCss }: { main: NavModelItem; customCss: string
|
|||||||
return navItem.active === true;
|
return navItem.active === true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const gotoUrl = evt => {
|
const gotoUrl = (evt: FormEvent) => {
|
||||||
const element = evt.target;
|
const element = evt.target as HTMLSelectElement;
|
||||||
const url = element.options[element.selectedIndex].value;
|
const url = element.options[element.selectedIndex].value;
|
||||||
appEvents.emit('location-change', { href: url });
|
appEvents.emit('location-change', { href: url });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pageName: string;
|
pageName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageLoader: SFC<Props> = ({ pageName }) => {
|
const PageLoader: FC<Props> = ({ pageName }) => {
|
||||||
const loadingText = `Loading ${pageName}...`;
|
const loadingText = `Loading ${pageName}...`;
|
||||||
return (
|
return (
|
||||||
<div className="page-loader-wrapper">
|
<div className="page-loader-wrapper">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { FormLabel, Select } from '@grafana/ui';
|
||||||
import { Select } from '@grafana/ui';
|
|
||||||
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
import { DashboardSearchHit } from 'app/types';
|
import { DashboardSearchHit } from 'app/types';
|
||||||
@@ -100,12 +99,12 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<Label
|
<FormLabel
|
||||||
width={11}
|
width={11}
|
||||||
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
|
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
|
||||||
>
|
>
|
||||||
Home Dashboard
|
Home Dashboard
|
||||||
</Label>
|
</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
|
value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
|
||||||
getOptionValue={i => i.id}
|
getOptionValue={i => i.id}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC, ReactNode, PureComponent } from 'react';
|
import React, { FC, ReactNode, PureComponent } from 'react';
|
||||||
import { Tooltip } from '@grafana/ui';
|
import { Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
interface ToggleButtonGroupProps {
|
interface ToggleButtonGroupProps {
|
||||||
@@ -29,7 +29,7 @@ interface ToggleButtonProps {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToggleButton: SFC<ToggleButtonProps> = ({
|
export const ToggleButton: FC<ToggleButtonProps> = ({
|
||||||
children,
|
children,
|
||||||
selected,
|
selected,
|
||||||
className = '',
|
className = '',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
child: any;
|
child: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropDownChild: SFC<Props> = props => {
|
const DropDownChild: FC<Props> = props => {
|
||||||
const { child } = props;
|
const { child } = props;
|
||||||
const listItemClassName = child.divider ? 'divider' : '';
|
const listItemClassName = child.divider ? 'divider' : '';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import DropDownChild from './DropDownChild';
|
import DropDownChild from './DropDownChild';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
link: any;
|
link: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideMenuDropDown: SFC<Props> = props => {
|
const SideMenuDropDown: FC<Props> = props => {
|
||||||
const { link } = props;
|
const { link } = props;
|
||||||
return (
|
return (
|
||||||
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu">
|
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
const SignIn: SFC<any> = () => {
|
const SignIn: FC<any> = () => {
|
||||||
const loginUrl = `login?redirect=${encodeURIComponent(window.location.pathname)}`;
|
const loginUrl = `login?redirect=${encodeURIComponent(window.location.pathname)}`;
|
||||||
return (
|
return (
|
||||||
<div className="sidemenu-item">
|
<div className="sidemenu-item">
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TopSectionItem from './TopSectionItem';
|
import TopSectionItem from './TopSectionItem';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
|
|
||||||
const TopSection: SFC<any> = () => {
|
const TopSection: FC<any> = () => {
|
||||||
const navTree = _.cloneDeep(config.bootData.navTree);
|
const navTree = _.cloneDeep(config.bootData.navTree);
|
||||||
const mainLinks = _.filter(navTree, item => !item.hideFromMenu);
|
const mainLinks = _.filter(navTree, item => !item.hideFromMenu);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import SideMenuDropDown from './SideMenuDropDown';
|
import SideMenuDropDown from './SideMenuDropDown';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
link: any;
|
link: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopSectionItem: SFC<Props> = props => {
|
const TopSectionItem: FC<Props> = props => {
|
||||||
const { link } = props;
|
const { link } = props;
|
||||||
return (
|
return (
|
||||||
<div className="sidemenu-item dropdown">
|
<div className="sidemenu-item dropdown">
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export interface BuildInfo {
|
|||||||
commit: string;
|
commit: string;
|
||||||
isEnterprise: boolean;
|
isEnterprise: boolean;
|
||||||
env: string;
|
env: string;
|
||||||
|
latestVersion: string;
|
||||||
|
hasUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|||||||
@@ -41,3 +41,7 @@ export function getNavModel(navIndex: NavIndex, id: string, fallback?: NavModel)
|
|||||||
|
|
||||||
return getNotFoundModel();
|
return getNotFoundModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTitleFromNavModel = (navModel: NavModel) => {
|
||||||
|
return `${navModel.main.text}${navModel.node.text ? ': ' + navModel.node.text : '' }`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import { getMultipleMockKeys, getMockKey } from './__mocks__/apiKeysMock';
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Api Keys'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
apiKeys: [] as ApiKey[],
|
apiKeys: [] as ApiKey[],
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
hasFetched: false,
|
hasFetched: false,
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { NavModel, ApiKey, NewApiKey, OrgRole } from 'app/types';
|
|||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
||||||
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
|
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
|
||||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
|
||||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||||
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
@@ -240,18 +239,17 @@ export class ApiKeysPage extends PureComponent<Props, any> {
|
|||||||
const { hasFetched, navModel, apiKeysCount } = this.props;
|
const { hasFetched, navModel, apiKeysCount } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={!hasFetched}>
|
||||||
{hasFetched ? (
|
{hasFetched && (
|
||||||
apiKeysCount > 0 ? (
|
apiKeysCount > 0 ? (
|
||||||
this.renderApiKeyList()
|
this.renderApiKeyList()
|
||||||
) : (
|
) : (
|
||||||
this.renderEmptyList()
|
this.renderEmptyList()
|
||||||
)
|
)
|
||||||
) : (
|
)}
|
||||||
<PageLoader pageName="Api keys" />
|
</Page.Contents>
|
||||||
)}
|
</Page>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +1,152 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render API keys table if there are any keys 1`] = `
|
exports[`Render should render API keys table if there are any keys 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"text": "Configuration",
|
||||||
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Api Keys",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
/>
|
/>
|
||||||
<PageLoader
|
</Page>
|
||||||
pageName="Api keys"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render CTA if there are no API keys 1`] = `
|
exports[`Render should render CTA if there are no API keys 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Api Keys",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<EmptyListCTA
|
<div
|
||||||
model={
|
className="page-container page-body"
|
||||||
Object {
|
|
||||||
"buttonIcon": "fa fa-plus",
|
|
||||||
"buttonLink": "#",
|
|
||||||
"buttonTitle": " New API Key",
|
|
||||||
"onClick": [Function],
|
|
||||||
"proTip": "Remember you can provide view-only API access to other applications.",
|
|
||||||
"proTipLink": "",
|
|
||||||
"proTipLinkTitle": "",
|
|
||||||
"proTipTarget": "_blank",
|
|
||||||
"title": "You haven't added any API Keys yet.",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
in={false}
|
|
||||||
>
|
>
|
||||||
<div
|
<EmptyListCTA
|
||||||
className="cta-form"
|
model={
|
||||||
|
Object {
|
||||||
|
"buttonIcon": "fa fa-plus",
|
||||||
|
"buttonLink": "#",
|
||||||
|
"buttonTitle": " New API Key",
|
||||||
|
"onClick": [Function],
|
||||||
|
"proTip": "Remember you can provide view-only API access to other applications.",
|
||||||
|
"proTipLink": "",
|
||||||
|
"proTipLinkTitle": "",
|
||||||
|
"proTipTarget": "_blank",
|
||||||
|
"title": "You haven't added any API Keys yet.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
in={false}
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
className="cta-form__close btn btn-transparent"
|
className="cta-form"
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<i
|
<button
|
||||||
className="fa fa-close"
|
className="cta-form__close btn btn-transparent"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
<h5>
|
<i
|
||||||
Add API Key
|
className="fa fa-close"
|
||||||
</h5>
|
/>
|
||||||
<form
|
</button>
|
||||||
className="gf-form-group"
|
<h5>
|
||||||
onSubmit={[Function]}
|
Add API Key
|
||||||
>
|
</h5>
|
||||||
<div
|
<form
|
||||||
className="gf-form-inline"
|
className="gf-form-group"
|
||||||
|
onSubmit={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="gf-form max-width-21"
|
className="gf-form-inline"
|
||||||
>
|
>
|
||||||
<span
|
<div
|
||||||
className="gf-form-label"
|
className="gf-form max-width-21"
|
||||||
>
|
>
|
||||||
Key name
|
<span
|
||||||
</span>
|
className="gf-form-label"
|
||||||
<input
|
|
||||||
className="gf-form-input"
|
|
||||||
onChange={[Function]}
|
|
||||||
placeholder="Name"
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="gf-form"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="gf-form-label"
|
|
||||||
>
|
|
||||||
Role
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="gf-form-select-wrapper"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
className="gf-form-input gf-size-auto"
|
|
||||||
onChange={[Function]}
|
|
||||||
value="Viewer"
|
|
||||||
>
|
>
|
||||||
<option
|
Key name
|
||||||
key="Viewer"
|
</span>
|
||||||
label="Viewer"
|
<input
|
||||||
|
className="gf-form-input"
|
||||||
|
onChange={[Function]}
|
||||||
|
placeholder="Name"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="gf-form-label"
|
||||||
|
>
|
||||||
|
Role
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="gf-form-select-wrapper"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
className="gf-form-input gf-size-auto"
|
||||||
|
onChange={[Function]}
|
||||||
value="Viewer"
|
value="Viewer"
|
||||||
>
|
>
|
||||||
Viewer
|
<option
|
||||||
</option>
|
key="Viewer"
|
||||||
<option
|
label="Viewer"
|
||||||
key="Editor"
|
value="Viewer"
|
||||||
label="Editor"
|
>
|
||||||
value="Editor"
|
Viewer
|
||||||
>
|
</option>
|
||||||
Editor
|
<option
|
||||||
</option>
|
key="Editor"
|
||||||
<option
|
label="Editor"
|
||||||
key="Admin"
|
value="Editor"
|
||||||
label="Admin"
|
>
|
||||||
value="Admin"
|
Editor
|
||||||
>
|
</option>
|
||||||
Admin
|
<option
|
||||||
</option>
|
key="Admin"
|
||||||
</select>
|
label="Admin"
|
||||||
</span>
|
value="Admin"
|
||||||
</div>
|
>
|
||||||
<div
|
Admin
|
||||||
className="gf-form"
|
</option>
|
||||||
>
|
</select>
|
||||||
<button
|
</span>
|
||||||
className="btn gf-form-btn btn-success"
|
</div>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
>
|
>
|
||||||
Add
|
<button
|
||||||
</button>
|
className="btn gf-form-btn btn-success"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</Component>
|
||||||
</Component>
|
</div>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { PanelMenuItem } from '@grafana/ui';
|
import { PanelMenuItem } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: any;
|
children: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelHeaderMenuItem: SFC<Props & PanelMenuItem> = props => {
|
export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
|
||||||
const isSubMenu = props.type === 'submenu';
|
const isSubMenu = props.type === 'submenu';
|
||||||
const isDivider = props.type === 'divider';
|
const isDivider = props.type === 'divider';
|
||||||
return isDivider ? (
|
return isDivider ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Tooltip } from '@grafana/ui';
|
import { Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -10,7 +10,7 @@ interface Props {
|
|||||||
tooltipInfo?: any;
|
tooltipInfo?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataSourceOptions: SFC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
|
export const DataSourceOptions: FC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
|
||||||
const dsOption = (
|
const dsOption = (
|
||||||
<div className="gf-form gf-form--flex-end">
|
<div className="gf-form gf-form--flex-end">
|
||||||
<label className="gf-form-label">{label}</label>
|
<label className="gf-form-label">{label}</label>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Input } from 'app/core/components/Form';
|
|||||||
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
||||||
import { InputStatus } from 'app/core/components/Form/Input';
|
import { InputStatus } from 'app/core/components/Form/Input';
|
||||||
import DataSourceOption from './DataSourceOption';
|
import DataSourceOption from './DataSourceOption';
|
||||||
import { GfFormLabel } from '@grafana/ui';
|
import { FormLabel } from '@grafana/ui';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelModel } from '../panel_model';
|
import { PanelModel } from '../panel_model';
|
||||||
@@ -164,7 +164,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
|||||||
{this.renderOptions()}
|
{this.renderOptions()}
|
||||||
|
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<GfFormLabel>Relative time</GfFormLabel>
|
<FormLabel>Relative time</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
className="width-6"
|
className="width-6"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { PluginDashboard } from '../../types';
|
import { PluginDashboard } from '../../types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -7,7 +7,7 @@ export interface Props {
|
|||||||
onRemove: (dashboard) => void;
|
onRemove: (dashboard) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardsTable: SFC<Props> = ({ dashboards, onImport, onRemove }) => {
|
const DashboardsTable: FC<Props> = ({ dashboards, onImport, onRemove }) => {
|
||||||
function buttonText(dashboard: PluginDashboard) {
|
function buttonText(dashboard: PluginDashboard) {
|
||||||
return dashboard.revision !== dashboard.importedRevision ? 'Update' : 'Re-import';
|
return dashboard.revision !== dashboard.importedRevision ? 'Update' : 'Re-import';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,14 @@ const setup = (propOverrides?: object) => {
|
|||||||
dataSources: [] as DataSource[],
|
dataSources: [] as DataSource[],
|
||||||
layoutMode: LayoutModes.Grid,
|
layoutMode: LayoutModes.Grid,
|
||||||
loadDataSources: jest.fn(),
|
loadDataSources: jest.fn(),
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Data Sources'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
dataSourcesCount: 0,
|
dataSourcesCount: 0,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
setDataSourcesSearchQuery: jest.fn(),
|
setDataSourcesSearchQuery: jest.fn(),
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import PageHeader from '../../core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
|
||||||
import OrgActionBar from '../../core/components/OrgActionBar/OrgActionBar';
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
|
|
||||||
import DataSourcesList from './DataSourcesList';
|
import DataSourcesList from './DataSourcesList';
|
||||||
import { DataSource, NavModel } from 'app/types';
|
import { DataSource, NavModel, StoreState } from 'app/types';
|
||||||
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
|
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||||
import { loadDataSources, setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
|
import { loadDataSources, setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
|
||||||
import { getNavModel } from '../../core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDataSources,
|
getDataSources,
|
||||||
getDataSourcesCount,
|
getDataSourcesCount,
|
||||||
@@ -67,30 +67,30 @@ export class DataSourcesListPage extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={!hasFetched}>
|
||||||
<div className="page-container page-body">
|
<>
|
||||||
{!hasFetched && <PageLoader pageName="Data sources" />}
|
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA model={emptyListModel} />}
|
||||||
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA model={emptyListModel} />}
|
{hasFetched &&
|
||||||
{hasFetched &&
|
dataSourcesCount > 0 && [
|
||||||
dataSourcesCount > 0 && [
|
<OrgActionBar
|
||||||
<OrgActionBar
|
layoutMode={layoutMode}
|
||||||
layoutMode={layoutMode}
|
searchQuery={searchQuery}
|
||||||
searchQuery={searchQuery}
|
onSetLayoutMode={mode => setDataSourcesLayoutMode(mode)}
|
||||||
onSetLayoutMode={mode => setDataSourcesLayoutMode(mode)}
|
setSearchQuery={query => setDataSourcesSearchQuery(query)}
|
||||||
setSearchQuery={query => setDataSourcesSearchQuery(query)}
|
linkButton={linkButton}
|
||||||
linkButton={linkButton}
|
key="action-bar"
|
||||||
key="action-bar"
|
/>,
|
||||||
/>,
|
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
|
||||||
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
|
]}
|
||||||
]}
|
</>
|
||||||
</div>
|
</Page.Contents>
|
||||||
</div>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state: StoreState) {
|
||||||
return {
|
return {
|
||||||
navModel: getNavModel(state.navIndex, 'datasources'),
|
navModel: getNavModel(state.navIndex, 'datasources'),
|
||||||
dataSources: getDataSources(state.dataSources),
|
dataSources: getDataSources(state.dataSources),
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render action bar and datasources 1`] = `
|
exports[`Render should render action bar and datasources 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Data Sources",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<OrgActionBar
|
<OrgActionBar
|
||||||
key="action-bar"
|
key="action-bar"
|
||||||
@@ -143,21 +151,25 @@ exports[`Render should render action bar and datasources 1`] = `
|
|||||||
key="list"
|
key="list"
|
||||||
layoutMode="grid"
|
layoutMode="grid"
|
||||||
/>
|
/>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"text": "Configuration",
|
||||||
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Data Sources",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
/>
|
/>
|
||||||
<div
|
</Page>
|
||||||
className="page-container page-body"
|
|
||||||
>
|
|
||||||
<PageLoader
|
|
||||||
pageName="Data sources"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { FormLabel } from '@grafana/ui';
|
||||||
import { Switch } from '../../../core/components/Switch/Switch';
|
import { Switch } from '../../../core/components/Switch/Switch';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -9,19 +9,19 @@ export interface Props {
|
|||||||
onDefaultChange: (value: boolean) => void;
|
onDefaultChange: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicSettings: SFC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => {
|
const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => {
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<div className="gf-form max-width-30" style={{ marginRight: '3px' }}>
|
<div className="gf-form max-width-30" style={{ marginRight: '3px' }}>
|
||||||
<Label
|
<FormLabel
|
||||||
tooltip={
|
tooltip={
|
||||||
'The name is used when you select the data source in panels. The Default data source is ' +
|
'The name is used when you select the data source in panels. The Default data source is ' +
|
||||||
'preselected in new panels.'
|
'preselected in new panels.'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
</Label>
|
</FormLabel>
|
||||||
<input
|
<input
|
||||||
className="gf-form-input max-width-23"
|
className="gf-form-input max-width-23"
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
@@ -6,7 +6,7 @@ export interface Props {
|
|||||||
onSubmit: (event) => void;
|
onSubmit: (event) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonRow: SFC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
|
const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-button-row">
|
<div className="gf-form-button-row">
|
||||||
<button type="submit" className="btn btn-success" disabled={isReadOnly} onClick={event => onSubmit(event)}>
|
<button type="submit" className="btn btn-success" disabled={isReadOnly} onClick={event => onSubmit(event)}>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: any;
|
message: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Alert: SFC<Props> = props => {
|
export const Alert: FC<Props> = props => {
|
||||||
const { message } = props;
|
const { message } = props;
|
||||||
return (
|
return (
|
||||||
<div className="gf-form-group section">
|
<div className="gf-form-group section">
|
||||||
|
|||||||
@@ -161,11 +161,17 @@ export function initializeExplore(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exploreDatasources.length > 1) {
|
if (exploreDatasources.length >= 1) {
|
||||||
let instance;
|
let instance;
|
||||||
if (datasource) {
|
if (datasource) {
|
||||||
instance = await getDatasourceSrv().get(datasource);
|
try {
|
||||||
} else {
|
instance = await getDatasourceSrv().get(datasource);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Checking on instance here because requested datasource could be deleted already
|
||||||
|
if (!instance) {
|
||||||
instance = await getDatasourceSrv().get();
|
instance = await getDatasourceSrv().get();
|
||||||
}
|
}
|
||||||
dispatch(loadDatasource(exploreId, instance));
|
dispatch(loadDatasource(exploreId, instance));
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import { NavModel, Organization } from '../../types';
|
|||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
organization: {} as Organization,
|
organization: {} as Organization,
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Org details'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
loadOrganization: jest.fn(),
|
loadOrganization: jest.fn(),
|
||||||
setOrganizationName: jest.fn(),
|
setOrganizationName: jest.fn(),
|
||||||
updateOrganization: jest.fn(),
|
updateOrganization: jest.fn(),
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PageHeader from '../../core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import PageLoader from '../../core/components/PageLoader/PageLoader';
|
|
||||||
import OrgProfile from './OrgProfile';
|
import OrgProfile from './OrgProfile';
|
||||||
import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences';
|
import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences';
|
||||||
import { loadOrganization, setOrganizationName, updateOrganization } from './state/actions';
|
import { loadOrganization, setOrganizationName, updateOrganization } from './state/actions';
|
||||||
import { NavModel, Organization, StoreState } from 'app/types';
|
import { NavModel, Organization, StoreState } from 'app/types';
|
||||||
import { getNavModel } from '../../core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
navModel: NavModel;
|
navModel: NavModel;
|
||||||
@@ -35,22 +34,22 @@ export class OrgDetailsPage extends PureComponent<Props> {
|
|||||||
const isLoading = Object.keys(organization).length === 0;
|
const isLoading = Object.keys(organization).length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={isLoading}>
|
||||||
<div className="page-container page-body">
|
<div className="page-container page-body">
|
||||||
{isLoading && <PageLoader pageName="Organization" />}
|
{!isLoading && (
|
||||||
{!isLoading && (
|
<div>
|
||||||
<div>
|
<OrgProfile
|
||||||
<OrgProfile
|
onOrgNameChange={name => this.onOrgNameChange(name)}
|
||||||
onOrgNameChange={name => this.onOrgNameChange(name)}
|
onSubmit={this.onUpdateOrganization}
|
||||||
onSubmit={this.onUpdateOrganization}
|
orgName={organization.name}
|
||||||
orgName={organization.name}
|
/>
|
||||||
/>
|
<SharedPreferences resourceUri="org" />
|
||||||
<SharedPreferences resourceUri="org" />
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Page.Contents>
|
||||||
</div>
|
</Page>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
orgName: string;
|
orgName: string;
|
||||||
@@ -6,7 +6,7 @@ export interface Props {
|
|||||||
onOrgNameChange: (orgName: string) => void;
|
onOrgNameChange: (orgName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrgProfile: SFC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
|
const OrgProfile: FC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="page-sub-heading">Organization profile</h3>
|
<h3 className="page-sub-heading">Organization profile</h3>
|
||||||
|
|||||||
@@ -1,38 +1,58 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Org details",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
>
|
>
|
||||||
<PageLoader
|
<div
|
||||||
pageName="Organization"
|
className="page-container page-body"
|
||||||
/>
|
/>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render organization and preferences 1`] = `
|
exports[`Render should render organization and preferences 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Org details",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<div>
|
<div
|
||||||
<OrgProfile
|
className="page-container page-body"
|
||||||
onOrgNameChange={[Function]}
|
>
|
||||||
onSubmit={[Function]}
|
<div>
|
||||||
orgName="Cool org"
|
<OrgProfile
|
||||||
/>
|
onOrgNameChange={[Function]}
|
||||||
<SharedPreferences
|
onSubmit={[Function]}
|
||||||
resourceUri="org"
|
orgName="Cool org"
|
||||||
/>
|
/>
|
||||||
|
<SharedPreferences
|
||||||
|
resourceUri="org"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PluginListItem from './PluginListItem';
|
import PluginListItem from './PluginListItem';
|
||||||
import { Plugin } from 'app/types';
|
import { Plugin } from 'app/types';
|
||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
layoutMode: LayoutMode;
|
layoutMode: LayoutMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginList: SFC<Props> = props => {
|
const PluginList: FC<Props> = props => {
|
||||||
const { plugins, layoutMode } = props;
|
const { plugins, layoutMode } = props;
|
||||||
|
|
||||||
const listStyle = classNames({
|
const listStyle = classNames({
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Plugin } from 'app/types';
|
import { Plugin } from 'app/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginListItem: SFC<Props> = props => {
|
const PluginListItem: FC<Props> = props => {
|
||||||
const { plugin } = props;
|
const { plugin } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Plugins'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
plugins: [] as Plugin[],
|
plugins: [] as Plugin[],
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
setPluginsSearchQuery: jest.fn(),
|
setPluginsSearchQuery: jest.fn(),
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
|
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
|
||||||
import PluginList from './PluginList';
|
import PluginList from './PluginList';
|
||||||
import { NavModel, Plugin } from 'app/types';
|
import { NavModel, Plugin } from 'app/types';
|
||||||
import { loadPlugins, setPluginsLayoutMode, setPluginsSearchQuery } from './state/actions';
|
import { loadPlugins, setPluginsLayoutMode, setPluginsSearchQuery } from './state/actions';
|
||||||
import { getNavModel } from '../../core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors';
|
import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors';
|
||||||
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
|
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
navModel: NavModel;
|
navModel: NavModel;
|
||||||
@@ -48,23 +47,22 @@ export class PluginListPage extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={!hasFetched}>
|
||||||
<div className="page-container page-body">
|
<>
|
||||||
<OrgActionBar
|
<OrgActionBar
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
layoutMode={layoutMode}
|
layoutMode={layoutMode}
|
||||||
onSetLayoutMode={mode => setPluginsLayoutMode(mode)}
|
onSetLayoutMode={mode => setPluginsLayoutMode(mode)}
|
||||||
setSearchQuery={query => setPluginsSearchQuery(query)}
|
setSearchQuery={query => setPluginsSearchQuery(query)}
|
||||||
linkButton={linkButton}
|
linkButton={linkButton}
|
||||||
/>
|
/>
|
||||||
{hasFetched ? (
|
{hasFetched && plugins && (
|
||||||
plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />
|
plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />
|
||||||
) : (
|
)}
|
||||||
<PageLoader pageName="Plugins" />
|
</>
|
||||||
)}
|
</Page.Contents>
|
||||||
</div>
|
</Page>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Plugins",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
>
|
>
|
||||||
<OrgActionBar
|
<OrgActionBar
|
||||||
layoutMode="grid"
|
layoutMode="grid"
|
||||||
@@ -20,20 +28,25 @@ exports[`Render should render component 1`] = `
|
|||||||
searchQuery=""
|
searchQuery=""
|
||||||
setSearchQuery={[Function]}
|
setSearchQuery={[Function]}
|
||||||
/>
|
/>
|
||||||
<PageLoader
|
</PageContents>
|
||||||
pageName="Plugins"
|
</Page>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render list 1`] = `
|
exports[`Render should render list 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Plugins",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<OrgActionBar
|
<OrgActionBar
|
||||||
layoutMode="grid"
|
layoutMode="grid"
|
||||||
@@ -51,6 +64,6 @@ exports[`Render should render list 1`] = `
|
|||||||
layoutMode="grid"
|
layoutMode="grid"
|
||||||
plugins={Array []}
|
plugins={Array []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks';
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Team List'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
teams: [] as Team[],
|
teams: [] as Team[],
|
||||||
loadTeams: jest.fn(),
|
loadTeams: jest.fn(),
|
||||||
deleteTeam: jest.fn(),
|
deleteTeam: jest.fn(),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import { DeleteButton } from '@grafana/ui';
|
import { DeleteButton } from '@grafana/ui';
|
||||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
import { NavModel, Team } from 'app/types';
|
||||||
import { NavModel, Team } from '../../types';
|
|
||||||
import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
|
import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
|
||||||
import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
|
import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
@@ -141,10 +140,11 @@ export class TeamList extends PureComponent<Props, any> {
|
|||||||
const { hasFetched, navModel } = this.props;
|
const { hasFetched, navModel } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={!hasFetched}>
|
||||||
{hasFetched ? this.renderList() : <PageLoader pageName="Teams" />}
|
{hasFetched && this.renderList()}
|
||||||
</div>
|
</Page.Contents>
|
||||||
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { FormLabel } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
|
||||||
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
|
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
|
||||||
import { updateTeam } from './state/actions';
|
import { updateTeam } from './state/actions';
|
||||||
import { getRouteParamsId } from 'app/core/selectors/location';
|
import { getRouteParamsId } from 'app/core/selectors/location';
|
||||||
@@ -51,7 +51,7 @@ export class TeamSettings extends React.Component<Props, State> {
|
|||||||
<h3 className="page-sub-heading">Team Settings</h3>
|
<h3 className="page-sub-heading">Team Settings</h3>
|
||||||
<form name="teamDetailsForm" className="gf-form-group" onSubmit={this.onUpdate}>
|
<form name="teamDetailsForm" className="gf-form-group" onSubmit={this.onUpdate}>
|
||||||
<div className="gf-form max-width-30">
|
<div className="gf-form max-width-30">
|
||||||
<Label>Name</Label>
|
<FormLabel>Name</FormLabel>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
@@ -62,9 +62,9 @@ export class TeamSettings extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="gf-form max-width-30">
|
<div className="gf-form max-width-30">
|
||||||
<Label tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)">
|
<FormLabel tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)">
|
||||||
Email
|
Email
|
||||||
</Label>
|
</FormLabel>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
className="gf-form-input max-width-22"
|
className="gf-form-input max-width-22"
|
||||||
|
|||||||
@@ -1,336 +1,356 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
|
"main": Object {
|
||||||
|
"text": "Configuration",
|
||||||
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Team List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
/>
|
/>
|
||||||
<PageLoader
|
</Page>
|
||||||
pageName="Teams"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render teams table 1`] = `
|
exports[`Render should render teams table 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Team List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="page-action-bar"
|
className="page-container page-body"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="gf-form gf-form--grow"
|
className="page-action-bar"
|
||||||
>
|
>
|
||||||
<label
|
<div
|
||||||
className="gf-form--has-input-icon gf-form--grow"
|
className="gf-form gf-form--grow"
|
||||||
>
|
>
|
||||||
<input
|
<label
|
||||||
className="gf-form-input"
|
className="gf-form--has-input-icon gf-form--grow"
|
||||||
onChange={[Function]}
|
>
|
||||||
placeholder="Search teams"
|
<input
|
||||||
type="text"
|
className="gf-form-input"
|
||||||
value=""
|
onChange={[Function]}
|
||||||
/>
|
placeholder="Search teams"
|
||||||
<i
|
type="text"
|
||||||
className="gf-form-input-icon fa fa-search"
|
value=""
|
||||||
/>
|
/>
|
||||||
</label>
|
<i
|
||||||
|
className="gf-form-input-icon fa fa-search"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="page-action-bar__spacer"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
className="btn btn-success"
|
||||||
|
href="org/teams/new"
|
||||||
|
>
|
||||||
|
New team
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="page-action-bar__spacer"
|
className="admin-list-table"
|
||||||
/>
|
|
||||||
<a
|
|
||||||
className="btn btn-success"
|
|
||||||
href="org/teams/new"
|
|
||||||
>
|
>
|
||||||
New team
|
<table
|
||||||
</a>
|
className="filter-table filter-table--hover form-inline"
|
||||||
</div>
|
>
|
||||||
<div
|
<thead>
|
||||||
className="admin-list-table"
|
<tr>
|
||||||
>
|
<th />
|
||||||
<table
|
<th>
|
||||||
className="filter-table filter-table--hover form-inline"
|
Name
|
||||||
>
|
</th>
|
||||||
<thead>
|
<th>
|
||||||
<tr>
|
Email
|
||||||
<th />
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Name
|
Members
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th
|
||||||
Email
|
style={
|
||||||
</th>
|
Object {
|
||||||
<th>
|
"width": "1%",
|
||||||
Members
|
}
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"width": "1%",
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
key="1"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
className="width-4 text-center link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/1"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="filter-table__avatar"
|
|
||||||
src="some/url/"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/1"
|
|
||||||
>
|
|
||||||
test-1
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/1"
|
|
||||||
>
|
|
||||||
test-1@test.com
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/1"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="text-right"
|
|
||||||
>
|
|
||||||
<DeleteButton
|
|
||||||
onConfirm={[Function]}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
<tr
|
<tbody>
|
||||||
key="2"
|
<tr
|
||||||
>
|
key="1"
|
||||||
<td
|
|
||||||
className="width-4 text-center link-td"
|
|
||||||
>
|
>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/2"
|
className="width-4 text-center link-td"
|
||||||
>
|
>
|
||||||
<img
|
<a
|
||||||
className="filter-table__avatar"
|
href="org/teams/edit/1"
|
||||||
src="some/url/"
|
>
|
||||||
|
<img
|
||||||
|
className="filter-table__avatar"
|
||||||
|
src="some/url/"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="org/teams/edit/1"
|
||||||
|
>
|
||||||
|
test-1
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="org/teams/edit/1"
|
||||||
|
>
|
||||||
|
test-1@test.com
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="org/teams/edit/1"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={[Function]}
|
||||||
/>
|
/>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td
|
<tr
|
||||||
className="link-td"
|
key="2"
|
||||||
>
|
>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/2"
|
className="width-4 text-center link-td"
|
||||||
>
|
>
|
||||||
test-2
|
<a
|
||||||
</a>
|
href="org/teams/edit/2"
|
||||||
</td>
|
>
|
||||||
<td
|
<img
|
||||||
className="link-td"
|
className="filter-table__avatar"
|
||||||
>
|
src="some/url/"
|
||||||
<a
|
/>
|
||||||
href="org/teams/edit/2"
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
>
|
>
|
||||||
test-2@test.com
|
<a
|
||||||
</a>
|
href="org/teams/edit/2"
|
||||||
</td>
|
>
|
||||||
<td
|
test-2
|
||||||
className="link-td"
|
</a>
|
||||||
>
|
</td>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/2"
|
className="link-td"
|
||||||
>
|
>
|
||||||
2
|
<a
|
||||||
</a>
|
href="org/teams/edit/2"
|
||||||
</td>
|
>
|
||||||
<td
|
test-2@test.com
|
||||||
className="text-right"
|
</a>
|
||||||
>
|
</td>
|
||||||
<DeleteButton
|
<td
|
||||||
onConfirm={[Function]}
|
className="link-td"
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="3"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
className="width-4 text-center link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/3"
|
|
||||||
>
|
>
|
||||||
<img
|
<a
|
||||||
className="filter-table__avatar"
|
href="org/teams/edit/2"
|
||||||
src="some/url/"
|
>
|
||||||
|
2
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={[Function]}
|
||||||
/>
|
/>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td
|
<tr
|
||||||
className="link-td"
|
key="3"
|
||||||
>
|
>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/3"
|
className="width-4 text-center link-td"
|
||||||
>
|
>
|
||||||
test-3
|
<a
|
||||||
</a>
|
href="org/teams/edit/3"
|
||||||
</td>
|
>
|
||||||
<td
|
<img
|
||||||
className="link-td"
|
className="filter-table__avatar"
|
||||||
>
|
src="some/url/"
|
||||||
<a
|
/>
|
||||||
href="org/teams/edit/3"
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
>
|
>
|
||||||
test-3@test.com
|
<a
|
||||||
</a>
|
href="org/teams/edit/3"
|
||||||
</td>
|
>
|
||||||
<td
|
test-3
|
||||||
className="link-td"
|
</a>
|
||||||
>
|
</td>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/3"
|
className="link-td"
|
||||||
>
|
>
|
||||||
3
|
<a
|
||||||
</a>
|
href="org/teams/edit/3"
|
||||||
</td>
|
>
|
||||||
<td
|
test-3@test.com
|
||||||
className="text-right"
|
</a>
|
||||||
>
|
</td>
|
||||||
<DeleteButton
|
<td
|
||||||
onConfirm={[Function]}
|
className="link-td"
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="4"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
className="width-4 text-center link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/4"
|
|
||||||
>
|
>
|
||||||
<img
|
<a
|
||||||
className="filter-table__avatar"
|
href="org/teams/edit/3"
|
||||||
src="some/url/"
|
>
|
||||||
|
3
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={[Function]}
|
||||||
/>
|
/>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td
|
<tr
|
||||||
className="link-td"
|
key="4"
|
||||||
>
|
>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/4"
|
className="width-4 text-center link-td"
|
||||||
>
|
>
|
||||||
test-4
|
<a
|
||||||
</a>
|
href="org/teams/edit/4"
|
||||||
</td>
|
>
|
||||||
<td
|
<img
|
||||||
className="link-td"
|
className="filter-table__avatar"
|
||||||
>
|
src="some/url/"
|
||||||
<a
|
/>
|
||||||
href="org/teams/edit/4"
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
>
|
>
|
||||||
test-4@test.com
|
<a
|
||||||
</a>
|
href="org/teams/edit/4"
|
||||||
</td>
|
>
|
||||||
<td
|
test-4
|
||||||
className="link-td"
|
</a>
|
||||||
>
|
</td>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/4"
|
className="link-td"
|
||||||
>
|
>
|
||||||
4
|
<a
|
||||||
</a>
|
href="org/teams/edit/4"
|
||||||
</td>
|
>
|
||||||
<td
|
test-4@test.com
|
||||||
className="text-right"
|
</a>
|
||||||
>
|
</td>
|
||||||
<DeleteButton
|
<td
|
||||||
onConfirm={[Function]}
|
className="link-td"
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="5"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
className="width-4 text-center link-td"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="org/teams/edit/5"
|
|
||||||
>
|
>
|
||||||
<img
|
<a
|
||||||
className="filter-table__avatar"
|
href="org/teams/edit/4"
|
||||||
src="some/url/"
|
>
|
||||||
|
4
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={[Function]}
|
||||||
/>
|
/>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td
|
<tr
|
||||||
className="link-td"
|
key="5"
|
||||||
>
|
>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/5"
|
className="width-4 text-center link-td"
|
||||||
>
|
>
|
||||||
test-5
|
<a
|
||||||
</a>
|
href="org/teams/edit/5"
|
||||||
</td>
|
>
|
||||||
<td
|
<img
|
||||||
className="link-td"
|
className="filter-table__avatar"
|
||||||
>
|
src="some/url/"
|
||||||
<a
|
/>
|
||||||
href="org/teams/edit/5"
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="link-td"
|
||||||
>
|
>
|
||||||
test-5@test.com
|
<a
|
||||||
</a>
|
href="org/teams/edit/5"
|
||||||
</td>
|
>
|
||||||
<td
|
test-5
|
||||||
className="link-td"
|
</a>
|
||||||
>
|
</td>
|
||||||
<a
|
<td
|
||||||
href="org/teams/edit/5"
|
className="link-td"
|
||||||
>
|
>
|
||||||
5
|
<a
|
||||||
</a>
|
href="org/teams/edit/5"
|
||||||
</td>
|
>
|
||||||
<td
|
test-5@test.com
|
||||||
className="text-right"
|
</a>
|
||||||
>
|
</td>
|
||||||
<DeleteButton
|
<td
|
||||||
onConfirm={[Function]}
|
className="link-td"
|
||||||
/>
|
>
|
||||||
</td>
|
<a
|
||||||
</tr>
|
href="org/teams/edit/5"
|
||||||
</tbody>
|
>
|
||||||
</table>
|
5
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={[Function]}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ jest.mock('../../core/app_events', () => ({
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
navModel: {} as NavModel,
|
navModel: {
|
||||||
|
main: {
|
||||||
|
text: 'Configuration'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
text: 'Users'
|
||||||
|
}
|
||||||
|
} as NavModel,
|
||||||
users: [] as OrgUser[],
|
users: [] as OrgUser[],
|
||||||
invitees: [] as Invitee[],
|
invitees: [] as Invitee[],
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import React, { PureComponent } from 'react';
|
|||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Remarkable from 'remarkable';
|
import Remarkable from 'remarkable';
|
||||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
|
||||||
import UsersActionBar from './UsersActionBar';
|
import UsersActionBar from './UsersActionBar';
|
||||||
import UsersTable from './UsersTable';
|
import UsersTable from './UsersTable';
|
||||||
import InviteesTable from './InviteesTable';
|
import InviteesTable from './InviteesTable';
|
||||||
import { Invitee, NavModel, OrgUser } from 'app/types';
|
import { Invitee, NavModel, OrgUser } from 'app/types';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { loadUsers, loadInvitees, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
|
import { loadUsers, loadInvitees, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
|
||||||
import { getNavModel } from '../../core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
|
import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -105,16 +104,17 @@ export class UsersListPage extends PureComponent<Props, State> {
|
|||||||
const externalUserMngInfoHtml = this.externalUserMngInfoHtml;
|
const externalUserMngInfoHtml = this.externalUserMngInfoHtml;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Page navModel={navModel}>
|
||||||
<PageHeader model={navModel} />
|
<Page.Contents isLoading={!hasFetched}>
|
||||||
<div className="page-container page-body">
|
<>
|
||||||
<UsersActionBar onShowInvites={this.onShowInvites} showInvites={this.state.showInvites} />
|
<UsersActionBar onShowInvites={this.onShowInvites} showInvites={this.state.showInvites} />
|
||||||
{externalUserMngInfoHtml && (
|
{externalUserMngInfoHtml && (
|
||||||
<div className="grafana-info-box" dangerouslySetInnerHTML={{ __html: externalUserMngInfoHtml }} />
|
<div className="grafana-info-box" dangerouslySetInnerHTML={{ __html: externalUserMngInfoHtml }} />
|
||||||
)}
|
)}
|
||||||
{hasFetched ? this.renderTable() : <PageLoader pageName="Users" />}
|
{hasFetched && this.renderTable()}
|
||||||
</div>
|
</>
|
||||||
</div>
|
</Page.Contents>
|
||||||
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { OrgUser } from 'app/types';
|
import { OrgUser } from 'app/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -7,7 +7,7 @@ export interface Props {
|
|||||||
onRemoveUser: (user: OrgUser) => void;
|
onRemoveUser: (user: OrgUser) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UsersTable: SFC<Props> = props => {
|
const UsersTable: FC<Props> = props => {
|
||||||
const { users, onRoleChange, onRemoveUser } = props;
|
const { users, onRoleChange, onRemoveUser } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render List page 1`] = `
|
exports[`Render should render List page 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Users",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={false}
|
||||||
>
|
>
|
||||||
<Connect(UsersActionBar)
|
<Connect(UsersActionBar)
|
||||||
onShowInvites={[Function]}
|
onShowInvites={[Function]}
|
||||||
@@ -17,25 +25,30 @@ exports[`Render should render List page 1`] = `
|
|||||||
onRoleChange={[Function]}
|
onRoleChange={[Function]}
|
||||||
users={Array []}
|
users={Array []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</PageContents>
|
||||||
</div>
|
</Page>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div>
|
<Page
|
||||||
<PageHeader
|
navModel={
|
||||||
model={Object {}}
|
Object {
|
||||||
/>
|
"main": Object {
|
||||||
<div
|
"text": "Configuration",
|
||||||
className="page-container page-body"
|
},
|
||||||
|
"node": Object {
|
||||||
|
"text": "Users",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageContents
|
||||||
|
isLoading={true}
|
||||||
>
|
>
|
||||||
<Connect(UsersActionBar)
|
<Connect(UsersActionBar)
|
||||||
onShowInvites={[Function]}
|
onShowInvites={[Function]}
|
||||||
showInvites={false}
|
showInvites={false}
|
||||||
/>
|
/>
|
||||||
<PageLoader
|
</PageContents>
|
||||||
pageName="Users"
|
</Page>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
@@ -14,7 +14,7 @@ export interface Props {
|
|||||||
usedAlignmentPeriod: string;
|
usedAlignmentPeriod: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlignmentPeriods: SFC<Props> = ({
|
export const AlignmentPeriods: FC<Props> = ({
|
||||||
alignmentPeriod,
|
alignmentPeriod,
|
||||||
templateSrv,
|
templateSrv,
|
||||||
onChange,
|
onChange,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||||
@@ -12,7 +12,7 @@ export interface Props {
|
|||||||
perSeriesAligner: string;
|
perSeriesAligner: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Alignments: SFC<Props> = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => {
|
export const Alignments: FC<Props> = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
export const AnnotationsHelp: SFC = () => {
|
export const AnnotationsHelp: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="gf-form grafana-info-box" style={{ padding: 0 }}>
|
<div className="gf-form grafana-info-box" style={{ padding: 0 }}>
|
||||||
<pre className="gf-form-pre alert alert-info" style={{ marginRight: 0 }}>
|
<pre className="gf-form-pre alert alert-info" style={{ marginRight: 0 }}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { SFC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onValueChange: (e) => void;
|
onValueChange: (e) => void;
|
||||||
@@ -7,7 +7,7 @@ interface Props {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SimpleSelect: SFC<Props> = props => {
|
const SimpleSelect: FC<Props> = props => {
|
||||||
const { label, onValueChange, value, options } = props;
|
const { label, onValueChange, value, options } = props;
|
||||||
return (
|
return (
|
||||||
<div className="gf-form max-width-21">
|
<div className="gf-form max-width-21">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { GaugeOptions, PanelOptionsProps, PanelOptionsGroup } from '@grafana/ui';
|
import { FormField, PanelOptionsProps, PanelOptionsGroup } from '@grafana/ui';
|
||||||
|
|
||||||
import { Switch } from 'app/core/components/Switch/Switch';
|
import { Switch } from 'app/core/components/Switch/Switch';
|
||||||
import { Label } from '../../../core/components/Label/Label';
|
import { GaugeOptions } from './types';
|
||||||
|
|
||||||
export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
||||||
onToggleThresholdLabels = () =>
|
onToggleThresholdLabels = () =>
|
||||||
@@ -21,14 +21,8 @@ export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelOptionsGroup title="Gauge">
|
<PanelOptionsGroup title="Gauge">
|
||||||
<div className="gf-form">
|
<FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={minValue} />
|
||||||
<Label width={8}>Min value</Label>
|
<FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={maxValue} />
|
||||||
<input type="text" className="gf-form-input width-12" onChange={this.onMinValueChange} value={minValue} />
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<Label width={8}>Max value</Label>
|
|
||||||
<input type="text" className="gf-form-input width-12" onChange={this.onMaxValueChange} value={maxValue} />
|
|
||||||
</div>
|
|
||||||
<Switch
|
<Switch
|
||||||
label="Show labels"
|
label="Show labels"
|
||||||
labelClass="width-8"
|
labelClass="width-8"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { GaugeOptions, PanelProps, NullValueMode } from '@grafana/ui';
|
import { PanelProps, NullValueMode } from '@grafana/ui';
|
||||||
|
|
||||||
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
||||||
import Gauge from 'app/viz/Gauge';
|
import Gauge from 'app/viz/Gauge';
|
||||||
|
import { GaugeOptions } from './types';
|
||||||
|
|
||||||
interface Props extends PanelProps<GaugeOptions> {}
|
interface Props extends PanelProps<GaugeOptions> {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import {
|
import {
|
||||||
BasicGaugeColor,
|
BasicGaugeColor,
|
||||||
GaugeOptions,
|
|
||||||
PanelOptionsProps,
|
PanelOptionsProps,
|
||||||
ThresholdsEditor,
|
ThresholdsEditor,
|
||||||
Threshold,
|
Threshold,
|
||||||
PanelOptionsGrid,
|
PanelOptionsGrid,
|
||||||
|
ValueMappingsEditor,
|
||||||
|
ValueMapping,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
|
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
|
||||||
import ValueMappings from 'app/plugins/panel/gauge/ValueMappings';
|
|
||||||
import GaugeOptionsEditor from './GaugeOptionsEditor';
|
import GaugeOptionsEditor from './GaugeOptionsEditor';
|
||||||
|
import { GaugeOptions } from './types';
|
||||||
|
|
||||||
export const defaultProps = {
|
export const defaultProps = {
|
||||||
options: {
|
options: {
|
||||||
@@ -24,7 +25,7 @@ export const defaultProps = {
|
|||||||
decimals: 0,
|
decimals: 0,
|
||||||
stat: 'avg',
|
stat: 'avg',
|
||||||
unit: 'none',
|
unit: 'none',
|
||||||
mappings: [],
|
valueMappings: [],
|
||||||
thresholds: [],
|
thresholds: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,7 +33,17 @@ export const defaultProps = {
|
|||||||
export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
||||||
static defaultProps = defaultProps;
|
static defaultProps = defaultProps;
|
||||||
|
|
||||||
onThresholdsChanged = (thresholds: Threshold[]) => this.props.onChange({ ...this.props.options, thresholds });
|
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.options,
|
||||||
|
thresholds,
|
||||||
|
});
|
||||||
|
|
||||||
|
onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.options,
|
||||||
|
valueMappings,
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onChange, options } = this.props;
|
const { onChange, options } = this.props;
|
||||||
@@ -44,7 +55,7 @@ export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<G
|
|||||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||||
</PanelOptionsGrid>
|
</PanelOptionsGrid>
|
||||||
|
|
||||||
<ValueMappings onChange={onChange} options={options} />
|
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { GaugeOptions, PanelOptionsProps, PanelOptionsGroup } from '@grafana/ui';
|
import { FormField, FormLabel, PanelOptionsProps, PanelOptionsGroup, Select } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
|
||||||
import { Select} from '@grafana/ui';
|
|
||||||
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
||||||
|
import { GaugeOptions } from './types';
|
||||||
|
|
||||||
const statOptions = [
|
const statOptions = [
|
||||||
{ value: 'min', label: 'Min' },
|
{ value: 'min', label: 'Min' },
|
||||||
@@ -42,7 +40,7 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
|
|||||||
return (
|
return (
|
||||||
<PanelOptionsGroup title="Value">
|
<PanelOptionsGroup title="Value">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<Label width={labelWidth}>Stat</Label>
|
<FormLabel width={labelWidth}>Stat</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
width={12}
|
width={12}
|
||||||
options={statOptions}
|
options={statOptions}
|
||||||
@@ -51,27 +49,19 @@ export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeO
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<Label width={labelWidth}>Unit</Label>
|
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form">
|
<FormField
|
||||||
<Label width={labelWidth}>Decimals</Label>
|
label="Decimals"
|
||||||
<input
|
labelWidth={labelWidth}
|
||||||
className="gf-form-input width-12"
|
placeholder="auto"
|
||||||
type="number"
|
onChange={this.onDecimalChange}
|
||||||
placeholder="auto"
|
value={decimals || ''}
|
||||||
value={decimals || ''}
|
type="number"
|
||||||
onChange={this.onDecimalChange}
|
/>
|
||||||
/>
|
<FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
|
||||||
</div>
|
<FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
|
||||||
<div className="gf-form">
|
|
||||||
<Label width={labelWidth}>Prefix</Label>
|
|
||||||
<input className="gf-form-input width-12" type="text" value={prefix || ''} onChange={this.onPrefixChange} />
|
|
||||||
</div>
|
|
||||||
<div className="gf-form">
|
|
||||||
<Label width={labelWidth}>Suffix</Label>
|
|
||||||
<input className="gf-form-input width-12" type="text" value={suffix || ''} onChange={this.onSuffixChange} />
|
|
||||||
</div>
|
|
||||||
</PanelOptionsGroup>
|
</PanelOptionsGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,16 @@
|
|||||||
|
import { Threshold, ValueMapping } from '@grafana/ui';
|
||||||
|
|
||||||
|
export interface GaugeOptions {
|
||||||
|
baseColor: string;
|
||||||
|
decimals: number;
|
||||||
|
valueMappings: ValueMapping[];
|
||||||
|
maxValue: number;
|
||||||
|
minValue: number;
|
||||||
|
prefix: string;
|
||||||
|
showThresholdLabels: boolean;
|
||||||
|
showThresholdMarkers: boolean;
|
||||||
|
stat: string;
|
||||||
|
suffix: string;
|
||||||
|
thresholds: Threshold[];
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const setup = (propOverrides?: object) => {
|
|||||||
const props: Props = {
|
const props: Props = {
|
||||||
baseColor: BasicGaugeColor.Green,
|
baseColor: BasicGaugeColor.Green,
|
||||||
maxValue: 100,
|
maxValue: 100,
|
||||||
mappings: [],
|
valueMappings: [],
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
prefix: '',
|
prefix: '',
|
||||||
showThresholdMarkers: true,
|
showThresholdMarkers: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { BasicGaugeColor, Threshold, TimeSeriesVMs, RangeMap, ValueMap, MappingType } from '@grafana/ui';
|
import { BasicGaugeColor, Threshold, TimeSeriesVMs, MappingType, ValueMapping } from '@grafana/ui';
|
||||||
|
|
||||||
import config from '../core/config';
|
import config from '../core/config';
|
||||||
import kbn from '../core/utils/kbn';
|
import kbn from '../core/utils/kbn';
|
||||||
@@ -9,7 +9,7 @@ export interface Props {
|
|||||||
baseColor: string;
|
baseColor: string;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
height: number;
|
height: number;
|
||||||
mappings: Array<RangeMap | ValueMap>;
|
valueMappings: ValueMapping[];
|
||||||
maxValue: number;
|
maxValue: number;
|
||||||
minValue: number;
|
minValue: number;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
@@ -29,7 +29,7 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
baseColor: BasicGaugeColor.Green,
|
baseColor: BasicGaugeColor.Green,
|
||||||
maxValue: 100,
|
maxValue: 100,
|
||||||
mappings: [],
|
valueMappings: [],
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
prefix: '',
|
prefix: '',
|
||||||
showThresholdMarkers: true,
|
showThresholdMarkers: true,
|
||||||
@@ -64,20 +64,17 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
return {
|
return { rangeMap, valueMap };
|
||||||
rangeMap,
|
|
||||||
valueMap,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(value) {
|
formatValue(value) {
|
||||||
const { decimals, mappings, prefix, suffix, unit } = this.props;
|
const { decimals, valueMappings, prefix, suffix, unit } = this.props;
|
||||||
|
|
||||||
const formatFunc = kbn.valueFormats[unit];
|
const formatFunc = kbn.valueFormats[unit];
|
||||||
const formattedValue = formatFunc(value, decimals);
|
const formattedValue = formatFunc(value, decimals);
|
||||||
|
|
||||||
if (mappings.length > 0) {
|
if (valueMappings.length > 0) {
|
||||||
const { rangeMap, valueMap } = this.formatWithMappings(mappings, formattedValue);
|
const { rangeMap, valueMap } = this.formatWithMappings(valueMappings, formattedValue);
|
||||||
|
|
||||||
if (valueMap) {
|
if (valueMap) {
|
||||||
return `${prefix} ${valueMap} ${suffix}`;
|
return `${prefix} ${valueMap} ${suffix}`;
|
||||||
@@ -148,10 +145,7 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
color: index === 0 ? threshold.color : thresholds[index].color,
|
color: index === 0 ? threshold.color : thresholds[index].color,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
{
|
{ value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor },
|
||||||
value: maxValue,
|
|
||||||
color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@@ -184,19 +178,14 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
formatter: () => {
|
formatter: () => {
|
||||||
return this.formatValue(value);
|
return this.formatValue(value);
|
||||||
},
|
},
|
||||||
font: {
|
font: { size: fontSize, family: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
|
||||||
size: fontSize,
|
|
||||||
family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const plotSeries = {
|
const plotSeries = { data: [[0, value]] };
|
||||||
data: [[0, value]],
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$.plot(this.canvasElement, [plotSeries], options);
|
$.plot(this.canvasElement, [plotSeries], options);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// DEPENDENCIES
|
// DEPENDENCIES
|
||||||
@import '../../node_modules/react-table/react-table.css';
|
@import '../../node_modules/react-table/react-table.css';
|
||||||
|
|
||||||
// VENDOR
|
// VENDOR
|
||||||
@@ -97,7 +97,6 @@
|
|||||||
@import 'components/add_data_source.scss';
|
@import 'components/add_data_source.scss';
|
||||||
@import 'components/page_loader';
|
@import 'components/page_loader';
|
||||||
@import 'components/toggle_button_group';
|
@import 'components/toggle_button_group';
|
||||||
@import 'components/value-mappings';
|
|
||||||
@import 'components/popover-box';
|
@import 'components/popover-box';
|
||||||
|
|
||||||
// LOAD @grafana/ui components
|
// LOAD @grafana/ui components
|
||||||
|
|||||||
@@ -38,6 +38,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-react .footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-react .custom-scrollbars .footer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
// Keeping footer inside the graphic on Login screen
|
// Keeping footer inside the graphic on Login screen
|
||||||
.login-page {
|
.login-page {
|
||||||
.footer {
|
.footer {
|
||||||
|
|||||||
@@ -20,7 +20,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-scrollbar-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-scrollbar-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding-left: $spacer*2;
|
padding-left: $spacer*2;
|
||||||
@@ -78,7 +94,6 @@
|
|||||||
|
|
||||||
.page-body {
|
.page-body {
|
||||||
padding-top: $spacer*2;
|
padding-top: $spacer*2;
|
||||||
min-height: 500px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-heading {
|
.page-heading {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ RUN git clone https://github.com/aptly-dev/aptly $GOPATH/src/github.com/aptly-de
|
|||||||
|
|
||||||
FROM circleci/python:2.7-stretch
|
FROM circleci/python:2.7-stretch
|
||||||
|
|
||||||
ENV PATH=$PATH:/opt/google-cloud-sdk/bin
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN pip install awscli && \
|
RUN pip install awscli && \
|
||||||
@@ -18,7 +16,9 @@ RUN pip install awscli && \
|
|||||||
apt update && \
|
apt update && \
|
||||||
apt install -y createrepo expect && \
|
apt install -y createrepo expect && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
ln -s /opt/google-cloud-sdk/bin/gsutil /usr/bin/gsutil && \
|
||||||
|
ln -s /opt/google-cloud-sdk/bin/gcloud /usr/bin/gcloud
|
||||||
|
|
||||||
COPY --from=0 /go/bin/aptly /usr/local/bin/aptly
|
COPY --from=0 /go/bin/aptly /usr/local/bin/aptly
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
_version="1.1.0"
|
_version="1.2.0"
|
||||||
_tag="grafana/grafana-ci-deploy:${_version}"
|
_tag="grafana/grafana-ci-deploy:${_version}"
|
||||||
|
|
||||||
docker build -t $_tag .
|
docker build -t $_tag .
|
||||||
|
|||||||
Reference in New Issue
Block a user