Merge branch 'master' into storybook/valuemappingseditor

This commit is contained in:
Peter Holmberg 2019-02-02 00:31:48 +01:00
commit a4d35e2fbc
18 changed files with 176 additions and 100 deletions

View File

@ -25,49 +25,71 @@ the latest master builds [here](https://grafana.com/grafana/download)
### Dependencies ### Dependencies
- Go (Latest Stable) - Go (Latest Stable)
- bra [`go get github.com/Unknwon/bra`]
- Node.js LTS - Node.js LTS
- yarn [`npm install -g yarn`]
### Get the project
**The project located in the go-path will be your working directory.**
### Building the backend
```bash ```bash
go get github.com/grafana/grafana go get github.com/grafana/grafana
cd $GOPATH/src/github.com/grafana/grafana cd $GOPATH/src/github.com/grafana/grafana
```
### Building
#### The backend
```bash
go run build.go setup go run build.go setup
go run build.go build go run build.go build
``` ```
### Building frontend assets #### Frontend assets
For this you need Node.js (LTS version). *For this you need Node.js (LTS version).*
To build the assets, rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000):
```bash ```bash
npm install -g yarn
yarn install --pure-lockfile yarn install --pure-lockfile
```
### Run and rebuild on source change
#### Backend
To run the backend and rebuild on source change:
```bash
$GOPATH/bin/bra run
```
#### Frontend
Rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000):
```bash
yarn watch yarn watch
``` ```
Build the assets, rebuild on file change with Hot Module Replacement (HMR), and serve them by webpack-dev-server (http://localhost:3333): Build the assets, rebuild on file change with Hot Module Replacement (HMR), and serve them by webpack-dev-server (http://localhost:3333):
```bash ```bash
yarn start yarn start
# OR set a theme # OR set a theme
env GRAFANA_THEME=light yarn start env GRAFANA_THEME=light yarn start
``` ```
Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload.
Run tests *Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload.*
Run tests and rebuild on source change:
```bash ```bash
yarn jest yarn jest
``` ```
### Recompile backend on source change **Open grafana in your browser (default: e.g. `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).**
To rebuild on source change.
```bash
go get github.com/Unknwon/bra
bra run
```
Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).
### Building a Docker image ### Building a Docker image

View File

@ -113,6 +113,9 @@ cache_mode = private
# Login cookie name # Login cookie name
cookie_name = grafana_session cookie_name = grafana_session
# Login cookie same site setting. defaults to `lax`. can be set to "lax", "strict" and "none"
cookie_samesite = lax
# How many days an session can be unused before we inactivate it # How many days an session can be unused before we inactivate it
login_remember_days = 7 login_remember_days = 7

View File

@ -109,6 +109,9 @@ log_queries =
# Login cookie name # Login cookie name
;cookie_name = grafana_session ;cookie_name = grafana_session
# Login cookie same site setting. defaults to `lax`. can be set to "lax", "strict" and "none"
;cookie_samesite = lax
# How many days an session can be unused before we inactivate it # How many days an session can be unused before we inactivate it
;login_remember_days = 7 ;login_remember_days = 7

View File

@ -167,6 +167,7 @@ $arrowSize: 15px;
color: inherit; color: inherit;
padding: 0; padding: 0;
border-radius: 10px; border-radius: 10px;
cursor: pointer;
} }
.sp-replacer:hover, .sp-replacer:hover,

View File

@ -1,26 +1,38 @@
// Libraries // Libraries
import React, { SFC } from 'react'; import React, { FunctionComponent } from 'react';
interface Props { interface Props {
title?: string; title?: string;
onClose?: () => void; onClose?: () => void;
children: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[] | boolean;
onAdd?: () => void;
} }
export const PanelOptionsGroup: SFC<Props> = props => { export const PanelOptionsGroup: FunctionComponent<Props> = props => {
return ( return (
<div className="panel-options-group"> <div className="panel-options-group">
{props.title && ( {props.onAdd ? (
<div className="panel-options-group__header"> <div className="panel-options-group__header">
{props.title} <button className="panel-options-group__add-btn" onClick={props.onAdd}>
{props.onClose && ( <div className="panel-options-group__add-circle">
<button className="btn btn-link" onClick={props.onClose}> <i className="fa fa-plus" />
<i className="fa fa-remove" /> </div>
</button> <span className="panel-options-group__title">{props.title}</span>
)} </button>
</div> </div>
) : (
props.title && (
<div className="panel-options-group__header">
<span className="panel-options-group__title">{props.title}</span>
{props.onClose && (
<button className="btn btn-link" onClick={props.onClose}>
<i className="fa fa-remove" />
</button>
)}
</div>
)
)} )}
<div className="panel-options-group__body">{props.children}</div> {props.children && <div className="panel-options-group__body">{props.children}</div>}
</div> </div>
); );
}; };

View File

@ -7,18 +7,57 @@
.panel-options-group__header { .panel-options-group__header {
padding: 4px 8px; padding: 4px 8px;
font-size: 1.1rem;
background: $panel-options-group-header-bg; background: $panel-options-group-header-bg;
position: relative; position: relative;
border-radius: $border-radius $border-radius 0 0; border-radius: $border-radius $border-radius 0 0;
display: flex;
align-items: center;
.btn { .btn {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0px; top: 0;
} }
} }
.panel-options-group__add-btn {
background: none;
border: none;
display: flex;
align-items: center;
padding: 0;
&:hover {
.panel-options-group__add-circle {
background-color: $btn-success-bg;
color: $text-color-strong;
}
}
}
.panel-options-group__add-circle {
@include gradientBar($btn-success-bg, $btn-success-bg-hl, $text-color);
border-radius: 50px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 6px;
i {
position: relative;
top: 1px;
}
}
.panel-options-group__title {
font-size: 1.1rem;
position: relative;
top: 1px;
}
.panel-options-group__body { .panel-options-group__body {
padding: 20px; padding: 20px;

View File

@ -1,11 +1,11 @@
.thresholds { .thresholds {
margin-bottom: 10px; margin-bottom: 20px;
} }
.thresholds-row { .thresholds-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 70px; height: 62px;
} }
.thresholds-row:first-child > .thresholds-row-color-indicator { .thresholds-row:first-child > .thresholds-row-color-indicator {
@ -21,21 +21,21 @@
} }
.thresholds-row-add-button { .thresholds-row-add-button {
@include buttonBackground($btn-success-bg, $btn-success-bg-hl, $text-color);
align-self: center; align-self: center;
margin-right: 5px; margin-right: 5px;
color: $green;
height: 24px; height: 24px;
width: 24px; width: 24px;
background-color: $green;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
}
.thresholds-row-add-button > i { &:hover {
color: $white; color: $text-color-strong;
}
} }
.thresholds-row-color-indicator { .thresholds-row-color-indicator {

View File

@ -2,7 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ValueMappingsEditor, Props } from './ValueMappingsEditor'; import { ValueMappingsEditor, Props } from './ValueMappingsEditor';
import { MappingType } from '../../types/panel'; import { MappingType } from '../../types';
const setup = (propOverrides?: object) => { const setup = (propOverrides?: object) => {
const props: Props = { const props: Props = {

View File

@ -1,8 +1,8 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import MappingRow from './MappingRow'; import MappingRow from './MappingRow';
import { MappingType, ValueMapping } from '../../types/panel'; import { MappingType, ValueMapping } from '../../types';
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; import { PanelOptionsGroup } from '..';
export interface Props { export interface Props {
valueMappings: ValueMapping[]; valueMappings: ValueMapping[];
@ -81,8 +81,7 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
const { valueMappings } = this.state; const { valueMappings } = this.state;
return ( return (
<PanelOptionsGroup title="Value Mappings"> <PanelOptionsGroup title="Add value mapping" onAdd={this.addMapping}>
<div>
{valueMappings.length > 0 && {valueMappings.length > 0 &&
valueMappings.map((valueMapping, index) => ( valueMappings.map((valueMapping, index) => (
<MappingRow <MappingRow
@ -92,13 +91,6 @@ export class ValueMappingsEditor extends PureComponent<Props, State> {
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)} removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
/> />
))} ))}
</div>
<div className="add-mapping-row" onClick={this.addMapping}>
<div className="add-mapping-row-icon">
<i className="fa fa-plus" />
</div>
<div className="add-mapping-row-label">Add mapping</div>
</div>
</PanelOptionsGroup> </PanelOptionsGroup>
); );
} }

View File

@ -2,55 +2,37 @@
exports[`Render should render component 1`] = ` exports[`Render should render component 1`] = `
<Component <Component
title="Value Mappings" onAdd={[Function]}
title="Add value mapping"
> >
<div> <MappingRow
<MappingRow key="Ok-0"
key="Ok-0" removeValueMapping={[Function]}
removeValueMapping={[Function]} updateValueMapping={[Function]}
updateValueMapping={[Function]} valueMapping={
valueMapping={ Object {
Object { "id": 1,
"id": 1, "operator": "",
"operator": "", "text": "Ok",
"text": "Ok", "type": 1,
"type": 1, "value": "20",
"value": "20",
}
} }
/> }
<MappingRow />
key="Meh-1" <MappingRow
removeValueMapping={[Function]} key="Meh-1"
updateValueMapping={[Function]} removeValueMapping={[Function]}
valueMapping={ updateValueMapping={[Function]}
Object { valueMapping={
"from": "21", Object {
"id": 2, "from": "21",
"operator": "", "id": 2,
"text": "Meh", "operator": "",
"to": "30", "text": "Meh",
"type": 2, "to": "30",
} "type": 2,
} }
/> }
</div> />
<div
className="add-mapping-row"
onClick={[Function]}
>
<div
className="add-mapping-row-icon"
>
<i
className="fa fa-plus"
/>
</div>
<div
className="add-mapping-row-label"
>
Add mapping
</div>
</div>
</Component> </Component>
`; `;

View File

@ -96,6 +96,7 @@ func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, va
Path: setting.AppSubUrl + "/", Path: setting.AppSubUrl + "/",
Secure: s.Cfg.SecurityHTTPSCookies, Secure: s.Cfg.SecurityHTTPSCookies,
MaxAge: maxAge, MaxAge: maxAge,
SameSite: s.Cfg.LoginCookieSameSite,
} }
http.SetCookie(ctx.Resp, &cookie) http.SetCookie(ctx.Resp, &cookie)

View File

@ -6,6 +6,7 @@ package setting
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -227,6 +228,7 @@ type Cfg struct {
LoginCookieMaxDays int LoginCookieMaxDays int
LoginCookieRotation int LoginCookieRotation int
LoginDeleteExpiredTokensAfterDays int LoginDeleteExpiredTokensAfterDays int
LoginCookieSameSite http.SameSite
SecurityHTTPSCookies bool SecurityHTTPSCookies bool
} }
@ -557,6 +559,20 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
cfg.LoginCookieName = login.Key("cookie_name").MustString("grafana_session") cfg.LoginCookieName = login.Key("cookie_name").MustString("grafana_session")
cfg.LoginCookieMaxDays = login.Key("login_remember_days").MustInt(7) cfg.LoginCookieMaxDays = login.Key("login_remember_days").MustInt(7)
cfg.LoginDeleteExpiredTokensAfterDays = login.Key("delete_expired_token_after_days").MustInt(30) cfg.LoginDeleteExpiredTokensAfterDays = login.Key("delete_expired_token_after_days").MustInt(30)
samesiteString := login.Key("cookie_samesite").MustString("lax")
validSameSiteValues := map[string]http.SameSite{
"lax": http.SameSiteLaxMode,
"strict": http.SameSiteStrictMode,
"none": http.SameSiteDefaultMode,
}
if samesite, ok := validSameSiteValues[samesiteString]; ok {
cfg.LoginCookieSameSite = samesite
} else {
cfg.LoginCookieSameSite = http.SameSiteLaxMode
}
cfg.LoginCookieRotation = login.Key("rotate_token_minutes").MustInt(10) cfg.LoginCookieRotation = login.Key("rotate_token_minutes").MustInt(10)
if cfg.LoginCookieRotation < 2 { if cfg.LoginCookieRotation < 2 {
cfg.LoginCookieRotation = 2 cfg.LoginCookieRotation = 2

View File

@ -249,7 +249,7 @@ export class KeybindingSrv {
if (panelInfo.panel.legend) { if (panelInfo.panel.legend) {
const panelRef = dashboard.getPanelById(dashboard.meta.focusPanelId); const panelRef = dashboard.getPanelById(dashboard.meta.focusPanelId);
panelRef.legend.show = !panelRef.legend.show; panelRef.legend.show = !panelRef.legend.show;
panelRef.refresh(); panelRef.render();
} }
} }
}); });

View File

@ -16,12 +16,12 @@ export class DataSourcesListItem extends PureComponent<Props> {
</div> </div>
<div className="card-item-body"> <div className="card-item-body">
<figure className="card-item-figure"> <figure className="card-item-figure">
<img src={dataSource.typeLogoUrl} /> <img src={dataSource.typeLogoUrl} alt={dataSource.name} />
</figure> </figure>
<div className="card-item-details"> <div className="card-item-details">
<div className="card-item-name"> <div className="card-item-name">
{dataSource.name} {dataSource.name}
{dataSource.isDefault && <span className="btn btn-secondary btn-mini">default</span>} {dataSource.isDefault && <span className="btn btn-secondary btn-mini card-item-label">default</span>}
</div> </div>
<div className="card-item-sub-name">{dataSource.url}</div> <div className="card-item-sub-name">{dataSource.url}</div>
</div> </div>

View File

@ -24,6 +24,7 @@ exports[`Render should render component 1`] = `
className="card-item-figure" className="card-item-figure"
> >
<img <img
alt="gdev-cloudwatch"
src="public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png" src="public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png"
/> />
</figure> </figure>

View File

@ -281,7 +281,7 @@ class GraphCtrl extends MetricsPanelCtrl {
toggleLegend() { toggleLegend() {
this.panel.legend.show = !this.panel.legend.show; this.panel.legend.show = !this.panel.legend.show;
this.refresh(); this.render();
} }
legendValuesOptionChanged() { legendValuesOptionChanged() {

View File

@ -3,7 +3,7 @@
<h5 class="section-heading">Options</h5> <h5 class="section-heading">Options</h5>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="Show" label-class="width-7" label="Show" label-class="width-7"
checked="ctrl.panel.legend.show" on-change="ctrl.refresh()"> checked="ctrl.panel.legend.show" on-change="ctrl.render()">
</gf-form-switch> </gf-form-switch>
<gf-form-switch class="gf-form" <gf-form-switch class="gf-form"
label="As Table" label-class="width-7" label="As Table" label-class="width-7"

View File

@ -109,6 +109,10 @@
width: 100%; width: 100%;
} }
.card-item-label {
margin-left: 8px;
}
.card-item-sub-name { .card-item-sub-name {
color: $text-color-weak; color: $text-color-weak;
overflow: hidden; overflow: hidden;