Merge branch 'master' into docs_v5.0

This commit is contained in:
Torkel Ödegaard
2018-02-21 10:47:14 +01:00
187 changed files with 5458 additions and 2418 deletions

View File

@@ -147,8 +147,7 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
<div className="alert-rule-item__body">
<div className="alert-rule-item__header">
<div className="alert-rule-item__name">
{rule.canEdit && <a href={ruleUrl}>{this.renderText(rule.name)}</a>}
{!rule.canEdit && <span>{this.renderText(rule.name)}</span>}
<a href={ruleUrl}>{this.renderText(rule.name)}</a>
</div>
<div className="alert-rule-item__text">
<span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span>
@@ -163,24 +162,12 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
className="btn btn-small btn-inverse alert-list__btn width-2"
title="Pausing an alert rule prevents it from executing"
onClick={this.toggleState}
disabled={!rule.canEdit}
>
<i className={stateClass} />
</button>
{rule.canEdit && (
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
<i className="icon-gf icon-gf-settings" />
</a>
)}
{!rule.canEdit && (
<button
className="btn btn-small btn-inverse alert-list__btn width-2"
title="Edit alert rule"
disabled={true}
>
<i className="icon-gf icon-gf-settings" />
</button>
)}
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
<i className="icon-gf icon-gf-settings" />
</a>
</div>
</li>
);

View File

@@ -82,7 +82,6 @@ exports[`AlertRuleList should render 1 rule 1`] = `
>
<button
className="btn btn-small btn-inverse alert-list__btn width-2"
disabled={false}
onClick={[Function]}
title="Pausing an alert rule prevents it from executing"
>

View File

@@ -87,6 +87,11 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
elem.toggleClass('playlist-active', newValue === true);
});
// check if we are in server side render
if (document.cookie.indexOf('renderKey') !== -1) {
body.addClass('body--phantomjs');
}
// tooltip removal fix
// manage page classes
var pageClass;

View File

@@ -1,5 +1,5 @@
<div class="dashboard-list">
<div class="page-action-bar page-action-bar--narrow" ng-hide="!ctrl.hasFilters && ctrl.sections.length === 0">
<div class="page-action-bar page-action-bar--narrow" ng-hide="ctrl.folderId && !ctrl.hasFilters && ctrl.sections.length === 0">
<label class="gf-form gf-form--grow gf-form--has-input-icon">
<input type="text" class="gf-form-input max-width-30" placeholder="Find Dashboard by name" tabindex="1" give-focus="true" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.onQueryChange()" />
<i class="gf-form-input-icon fa fa-search"></i>
@@ -52,6 +52,12 @@
</em>
</div>
<div class="search-results" ng-show="!ctrl.folderId && !ctrl.hasFilters && ctrl.sections.length === 0">
<em class="muted">
No dashboards found.
</em>
</div>
<div class="search-results" ng-show="ctrl.sections.length > 0">
<div class="search-results-filter-row">
<gf-form-switch
@@ -103,6 +109,7 @@
/>
</div>
</div>
</div>
<div ng-if="ctrl.canSave && ctrl.folderId && !ctrl.hasFilters && ctrl.sections.length === 0">

View File

@@ -37,7 +37,7 @@ export class ManageDashboardsCtrl {
folderUid?: string;
// if user can add new folders and/or add new dashboards
canSave: boolean;
canSave = false;
// if user has editor role or higher
isEditor: boolean;

View File

@@ -15,8 +15,7 @@ const template = `
</a>
</div>
<div class="modal-content">
<div class="gf-form-group">
<div class="modal-content modal-content--has-scroll" grafana-scrollbar>
<table class="filter-table form-inline">
<thead>
<tr>

View File

@@ -43,7 +43,7 @@
</tag-filter>
</div>
<div class="search-filter-box">
<div class="search-filter-box" ng-if="ctrl.isEditor">
<a href="dashboard/new" class="search-filter-box-link">
<i class="gicon gicon-dashboard-new"></i> New dashboard
</a>

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import coreModule from '../../core_module';
import { SearchSrv } from 'app/core/services/search_srv';
import { contextSrv } from 'app/core/services/context_srv';
import appEvents from 'app/core/app_events';
export class SearchCtrl {
@@ -15,6 +16,7 @@ export class SearchCtrl {
ignoreClose: any;
isLoading: boolean;
initialFolderFilterTitle: string;
isEditor: string;
/** @ngInject */
constructor($scope, private $location, private $timeout, private searchSrv: SearchSrv) {
@@ -24,6 +26,7 @@ export class SearchCtrl {
this.initialFolderFilterTitle = 'All';
this.getTags = this.getTags.bind(this);
this.onTagSelect = this.onTagSelect.bind(this);
this.isEditor = contextSrv.isEditor;
}
closeSearch() {

View File

@@ -20,7 +20,7 @@
<div class="search-section__header" ng-show="section.hideHeader"></div>
<div ng-if="section.expanded">
<a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}">
<a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}" >
<div ng-click="ctrl.toggleSelection(item, $event)">
<gf-form-switch
ng-show="ctrl.editable"

View File

@@ -12,7 +12,7 @@ export class InvitedCtrl {
icon: 'gicon gicon-branding',
text: 'Invite',
subTitle: 'Register your Grafana account',
breadcrumbs: [{ title: 'Login', url: '/login' }],
breadcrumbs: [{ title: 'Login', url: 'login' }],
},
};

View File

@@ -10,6 +10,13 @@ export class SignUpCtrl {
$scope.formModel = {};
var params = $location.search();
// validate email is semi ok
if (params.email && !params.email.match(/^\S+@\S+$/)) {
console.log('invalid email');
return;
}
$scope.formModel.orgName = params.email;
$scope.formModel.email = params.email;
$scope.formModel.username = params.email;
@@ -21,8 +28,9 @@ export class SignUpCtrl {
$scope.navModel = {
main: {
icon: 'gicon gicon-branding',
text: 'Sign Up',
subTitle: 'Register your Grafana account',
breadcrumbs: [{ title: 'Login', url: 'login' }, { title: 'Sign Up' }],
breadcrumbs: [{ title: 'Login', url: 'login' }],
},
};

View File

@@ -7,7 +7,7 @@ export class AlertSrv {
list: any[];
/** @ngInject */
constructor(private $timeout, private $rootScope, private $modal) {
constructor(private $timeout, private $rootScope) {
this.list = [];
}
@@ -39,7 +39,6 @@ export class AlertSrv {
appEvents.on('alert-warning', options => this.set(options[0], options[1], 'warning', 5000));
appEvents.on('alert-success', options => this.set(options[0], options[1], 'success', 3000));
appEvents.on('alert-error', options => this.set(options[0], options[1], 'error', 7000));
appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
}
getIconForSeverity(severity) {
@@ -96,45 +95,6 @@ export class AlertSrv {
clearAll() {
this.list = [];
}
showConfirmModal(payload) {
var scope = this.$rootScope.$new();
scope.onConfirm = function() {
payload.onConfirm();
scope.dismiss();
};
scope.updateConfirmText = function(value) {
scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase();
};
scope.title = payload.title;
scope.text = payload.text;
scope.text2 = payload.text2;
scope.confirmText = payload.confirmText;
scope.onConfirm = payload.onConfirm;
scope.onAltAction = payload.onAltAction;
scope.altActionText = payload.altActionText;
scope.icon = payload.icon || 'fa-check';
scope.yesText = payload.yesText || 'Yes';
scope.noText = payload.noText || 'Cancel';
scope.confirmTextValid = scope.confirmText ? false : true;
var confirmModal = this.$modal({
template: 'public/app/partials/confirm_modal.html',
persist: false,
modalClass: 'confirm-modal',
show: false,
scope: scope,
keyboard: false,
});
confirmModal.then(function(modalEl) {
modalEl.modal('show');
});
}
}
coreModule.service('alertSrv', AlertSrv);

View File

@@ -5,9 +5,11 @@ import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';
export class KeybindingSrv {
helpModal: boolean;
modalOpen = false;
/** @ngInject */
constructor(private $rootScope, private $location) {
@@ -19,6 +21,7 @@ export class KeybindingSrv {
});
this.setupGlobal();
appEvents.on('show-modal', () => (this.modalOpen = true));
}
setupGlobal() {
@@ -30,6 +33,7 @@ export class KeybindingSrv {
this.bind('s o', this.openSearch);
this.bind('s t', this.openSearchTags);
this.bind('f', this.openSearch);
this.bindGlobal('esc', this.exit);
}
openSearchStarred() {
@@ -60,6 +64,28 @@ export class KeybindingSrv {
appEvents.emit('show-modal', { templateHtml: '<help-modal></help-modal>' });
}
exit() {
var popups = $('.popover.in');
if (popups.length > 0) {
return;
}
appEvents.emit('hide-modal');
if (!this.modalOpen) {
this.$rootScope.appEvent('panel-change-view', { fullscreen: false, edit: false });
} else {
this.modalOpen = false;
}
// close settings view
var search = this.$location.search();
if (search.editview) {
delete search.editview;
this.$location.search(search);
}
}
bind(keyArg, fn) {
Mousetrap.bind(
keyArg,
@@ -73,6 +99,19 @@ export class KeybindingSrv {
);
}
bindGlobal(keyArg, fn) {
Mousetrap.bindGlobal(
keyArg,
evt => {
evt.preventDefault();
evt.stopPropagation();
evt.returnValue = false;
return this.$rootScope.$apply(fn.bind(this));
},
'keydown'
);
}
showDashEditView() {
var search = _.extend(this.$location.search(), { editview: 'settings' });
this.$location.search(search);
@@ -204,23 +243,6 @@ export class KeybindingSrv {
this.bind('d v', () => {
appEvents.emit('toggle-view-mode');
});
this.bind('esc', () => {
var popups = $('.popover.in');
if (popups.length > 0) {
return;
}
scope.appEvent('hide-modal');
scope.appEvent('panel-change-view', { fullscreen: false, edit: false });
// close settings view
var search = this.$location.search();
if (search.editview) {
delete search.editview;
this.$location.search(search);
}
});
}
}

View File

@@ -10,6 +10,7 @@ export class UtilSrv {
init() {
appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope);
appEvents.on('hide-modal', this.hideModal.bind(this), this.$rootScope);
appEvents.on('confirm-modal', this.showConfirmModal.bind(this), this.$rootScope);
}
hideModal() {
@@ -47,6 +48,38 @@ export class UtilSrv {
modalEl.modal('show');
});
}
showConfirmModal(payload) {
var scope = this.$rootScope.$new();
scope.onConfirm = function() {
payload.onConfirm();
scope.dismiss();
};
scope.updateConfirmText = function(value) {
scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase();
};
scope.title = payload.title;
scope.text = payload.text;
scope.text2 = payload.text2;
scope.confirmText = payload.confirmText;
scope.onConfirm = payload.onConfirm;
scope.onAltAction = payload.onAltAction;
scope.altActionText = payload.altActionText;
scope.icon = payload.icon || 'fa-check';
scope.yesText = payload.yesText || 'Yes';
scope.noText = payload.noText || 'Cancel';
scope.confirmTextValid = scope.confirmText ? false : true;
appEvents.emit('show-modal', {
src: 'public/app/partials/confirm_modal.html',
scope: scope,
modalClass: 'confirm-modal',
});
}
}
coreModule.service('utilSrv', UtilSrv);

View File

@@ -348,3 +348,10 @@ describe('duration', function() {
expect(str).toBe('1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds');
});
});
describe('volume', function() {
it('1000m3', function() {
var str = kbn.valueFormats['m3'](1000, 1, null);
expect(str).toBe('1000.0 m3');
});
});

View File

@@ -1,6 +1,12 @@
import { SearchCtrl } from '../components/search/search';
import { SearchSrv } from '../services/search_srv';
jest.mock('app/core/services/context_srv', () => ({
contextSrv: {
user: { orgId: 1 },
},
}));
describe('SearchCtrl', () => {
const searchSrvStub = {
search: (options: any) => {},

View File

@@ -547,8 +547,8 @@ kbn.valueFormats.accG = kbn.formatBuilders.fixedUnit('g');
// Volume
kbn.valueFormats.litre = kbn.formatBuilders.decimalSIPrefix('L');
kbn.valueFormats.mlitre = kbn.formatBuilders.decimalSIPrefix('L', -1);
kbn.valueFormats.m3 = kbn.formatBuilders.decimalSIPrefix('m3');
kbn.valueFormats.dm3 = kbn.formatBuilders.decimalSIPrefix('dm3');
kbn.valueFormats.m3 = kbn.formatBuilders.fixedUnit('m3');
kbn.valueFormats.dm3 = kbn.formatBuilders.fixedUnit('dm3');
kbn.valueFormats.gallons = kbn.formatBuilders.fixedUnit('gal');
// Flow

View File

@@ -75,7 +75,7 @@ export class AlertTabCtrl {
getAlertHistory() {
this.backendSrv
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50`)
.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
.then(res => {
this.alertHistory = _.map(res, ah => {
ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');

View File

@@ -58,15 +58,29 @@ export class AlertNotificationEditCtrl {
}
if (this.model.id) {
this.backendSrv.put(`/api/alert-notifications/${this.model.id}`, this.model).then(res => {
this.model = res;
appEvents.emit('alert-success', ['Notification updated', '']);
});
this.backendSrv
.put(`/api/alert-notifications/${this.model.id}`, this.model)
.then(res => {
this.model = res;
appEvents.emit('alert-success', ['Notification updated', '']);
})
.catch(err => {
if (err.data && err.data.error) {
appEvents.emit('alert-error', [err.data.error]);
}
});
} else {
this.backendSrv.post(`/api/alert-notifications`, this.model).then(res => {
appEvents.emit('alert-success', ['Notification created', '']);
this.$location.path('alerting/notifications');
});
this.backendSrv
.post(`/api/alert-notifications`, this.model)
.then(res => {
appEvents.emit('alert-success', ['Notification created', '']);
this.$location.path('alerting/notifications');
})
.catch(err => {
if (err.data && err.data.error) {
appEvents.emit('alert-error', [err.data.error]);
}
});
}
}

View File

@@ -18,7 +18,7 @@ export class DashboardMigrator {
}
updateSchema(old) {
var i, j, k;
var i, j, k, n;
var oldVersion = this.dashboard.schemaVersion;
var panelUpgrades = [];
this.dashboard.schemaVersion = 16;
@@ -63,11 +63,17 @@ export class DashboardMigrator {
}
if (panel.y_format) {
if (!panel.y_formats) {
panel.y_formats = [];
}
panel.y_formats[0] = panel.y_format;
delete panel.y_format;
}
if (panel.y2_format) {
if (!panel.y_formats) {
panel.y_formats = [];
}
panel.y_formats[1] = panel.y2_format;
delete panel.y2_format;
}
@@ -372,6 +378,11 @@ export class DashboardMigrator {
for (j = 0; j < this.dashboard.panels.length; j++) {
for (k = 0; k < panelUpgrades.length; k++) {
panelUpgrades[k].call(this, this.dashboard.panels[j]);
if (this.dashboard.panels[j].panels) {
for (n = 0; n < this.dashboard.panels[j].panels.length; n++) {
panelUpgrades[k].call(this, this.dashboard.panels[j].panels[n]);
}
}
}
}
}
@@ -429,6 +440,9 @@ export class DashboardMigrator {
for (let panel of row.panels) {
panel.span = panel.span || DEFAULT_PANEL_SPAN;
if (panel.minSpan) {
panel.minSpan = Math.min(GRID_COLUMN_COUNT, GRID_COLUMN_COUNT / 12 * panel.minSpan);
}
const panelWidth = Math.floor(panel.span) * widthFactor;
const panelHeight = panel.height ? getGridHeight(panel.height) : rowGridHeight;

View File

@@ -500,11 +500,12 @@ export class DashboardModel {
if (!rowPanel.panels || rowPanel.panels.length === 0) {
return 0;
}
const rowYPos = rowPanel.gridPos.y;
const positions = _.map(rowPanel.panels, 'gridPos');
const maxPos = _.maxBy(positions, pos => {
return pos.y + pos.h;
});
return maxPos.h + 1;
return maxPos.y + maxPos.h - rowYPos;
}
removePanel(panel: PanelModel) {

View File

@@ -77,8 +77,11 @@ export class DashboardSrv {
postSave(clone, data) {
this.dash.version = data.version;
if (data.url !== this.$location.path()) {
this.$location.url(locationUtil.stripBaseFromUrl(data.url)).replace();
const newUrl = locationUtil.stripBaseFromUrl(data.url);
const currentPath = this.$location.path();
if (newUrl !== currentPath) {
this.$location.url(newUrl).replace();
}
this.$rootScope.appEvent('dashboard-saved', this.dash);

View File

@@ -30,7 +30,13 @@ export class FolderPickerCtrl {
}
getOptions(query) {
return this.backendSrv.get('api/dashboards/folders', { query: query }).then(result => {
const params = {
query: query,
type: 'dash-folder',
permission: 'Edit',
};
return this.backendSrv.get('api/search', params).then(result => {
if (
query === '' ||
query.toLowerCase() === 'g' ||

View File

@@ -363,6 +363,22 @@ describe('DashboardModel', function() {
expect(dashboard.panels[0].repeat).toBe('server');
expect(dashboard.panels.length).toBe(2);
});
it('minSpan should be twice', function() {
model.rows = [createRow({ height: 8 }, [[6]])];
model.rows[0].panels[0] = { minSpan: 12 };
let dashboard = new DashboardModel(model);
expect(dashboard.panels[0].minSpan).toBe(24);
});
it('should assign id', function() {
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
model.rows[0].panels[0] = {};
let dashboard = new DashboardModel(model);
expect(dashboard.panels[0].id).toBe(1);
});
});
});

View File

@@ -500,6 +500,22 @@ describe('given dashboard with row repeat', function() {
);
expect(panel_ids.length).toEqual(_.uniq(panel_ids).length);
});
it('should place new panels in proper order', function() {
dashboardJSON.panels = [
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, h: 1, w: 24 }, repeat: 'apps' },
{ id: 2, type: 'graph', gridPos: { x: 0, y: 1, h: 3, w: 12 } },
{ id: 3, type: 'graph', gridPos: { x: 6, y: 1, h: 4, w: 12 } },
{ id: 4, type: 'graph', gridPos: { x: 0, y: 5, h: 2, w: 12 } },
];
dashboard = new DashboardModel(dashboardJSON);
dashboard.processRepeats();
const panel_types = _.map(dashboard.panels, 'type');
expect(panel_types).toEqual(['row', 'graph', 'graph', 'graph', 'row', 'graph', 'graph', 'graph']);
const panel_y_positions = _.map(dashboard.panels, p => p.gridPos.y);
expect(panel_y_positions).toEqual([0, 1, 1, 5, 7, 8, 8, 12]);
});
});
describe('given dashboard with row and panel repeat', () => {

View File

@@ -1,44 +1,26 @@
<div class="container">
<page-header model="navModel"></page-header>
<div class="signup-page-background">
</div>
<div class="page-container page-body">
<div class="login-content">
<div class="login-branding">
<img src="img/logo_transparent_200x75.png">
</div>
<div class="invite-box">
<h3>
<i class="fa fa-users"></i>&nbsp;
Change active organization
</h3>
<div class="signup">
<div class="login-form">
<div class="modal-tagline">
You have been added to another Organization <br>
due to an open invitation!
<br><br>
You have been added to another Organization due to an open invitation!
Please select which organization you want to <br>
use right now (you can change this later at any time).
</div>
<div style="display: inline-block; width: 400px; margin: 30px 0">
<table class="filter-table">
<tr ng-repeat="org in orgs">
<td class="nobg max-width-btns">
<a ng-click="setUsingOrg(org)" class="btn btn-inverse">
{{org.name}} ({{org.role}})
</a>
</td>
</tr>
</table>
<div ng-repeat="org in orgs">
<a ng-click="setUsingOrg(org)" class="btn btn-success">
{{org.name}} ({{org.role}})
</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -6,6 +6,14 @@ export class SelectOrgCtrl {
constructor($scope, backendSrv, contextSrv) {
contextSrv.sidemenu = false;
$scope.navModel = {
main: {
icon: 'gicon gicon-branding',
subTitle: 'Preferences',
text: 'Select active organization',
},
};
$scope.init = function() {
$scope.getUserOrgs();
};

View File

@@ -9,9 +9,7 @@
<a href="{{dash.importedUrl}}" ng-show="dash.imported">
{{dash.title}}
</a>
<span ng-show="!dash.imported">
{{dash.title}}
</span>
<span ng-show="!dash.imported">{{dash.title}}</span>
</td>
<td style="text-align: right">
<button class="btn btn-secondary btn-small" ng-click="ctrl.import(dash, false)" ng-show="!dash.imported">

View File

@@ -20,7 +20,7 @@
</div>
<div class="gf-form" ng-show="ctrl.panel.repeat">
<span class="gf-form-label width-9">Min width</span>
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]">
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
<option value=""></option>
</select>
</div>

View File

@@ -18,7 +18,7 @@
</div>
</form>
<div ng-if="mode === 'email-sent'">
<div ng-show="mode === 'email-sent'">
An email with a reset link as been sent to the email address. <br>
You should receive it shortly.
<div class="p-t-1">
@@ -27,5 +27,23 @@
</a>
</div>
</div>
<form name="resetForm" class="login-form gf-form-group" ng-show="mode === 'reset'">
<div class="gf-form">
<span class="gf-form-label width-9">New Password</span>
<input type="password" name="NewPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.newPassword' placeholder="password" watch-change="formModel.newPassword = inputValue;">
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Confirm Password</span>
<input type="password" name="ConfirmPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password">
</div>
<div class="signup__password-strength">
<password-strength password="formModel.newPassword"></password-strength>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="submitReset();" ng-disabled="!resetForm.$valid">
Reset Password
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,7 +1,7 @@
# CloudWatch Datasource - Native Plugin
# CloudWatch Data Source - Native Plugin
Grafana ships with **built in** support for CloudWatch. You just have to add it as a data source and you will be ready to build dashboards for you CloudWatch metrics.
Read more about it here:
[http://docs.grafana.org/datasources/cloudwatch/](http://docs.grafana.org/datasources/cloudwatch/)
[http://docs.grafana.org/datasources/cloudwatch/](http://docs.grafana.org/datasources/cloudwatch/)

View File

@@ -8,6 +8,7 @@
"annotations": true,
"info": {
"description": "Cloudwatch Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -15,6 +16,7 @@
"logos": {
"small": "img/amazon-web-services.png",
"large": "img/amazon-web-services.png"
}
},
"version": "5.0.0"
}
}

View File

@@ -1,4 +1,4 @@
# Elasticsearch Datasource - Native Plugin
# Elasticsearch Data Source - Native Plugin
Grafana ships with **advanced support** for Elasticsearch. You can do many types of simple or complex elasticsearch queries to visualize logs or metrics stored in Elasticsearch. You can also annotate your graphs with log events stored in Elasticsearch.

View File

@@ -166,7 +166,7 @@ export class ElasticDatasource {
for (var i = 0; i < hits.length; i++) {
var source = hits[i]._source;
var time = source[timeField];
var time = getFieldFromSource(source, timeField);
if (typeof hits[i].fields !== 'undefined') {
var fields = hits[i].fields;
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {

View File

@@ -17,7 +17,7 @@
"links": [
{"name": "elastic.co", "url": "https://www.elastic.co/products/elasticsearch"}
],
"version": "3.0.0"
"version": "5.0.0"
},
"annotations": true,

View File

@@ -6,4 +6,8 @@ Grafana has an advanced Graphite query editor that lets you quickly navigate the
Read more about it here:
[http://docs.grafana.org/datasources/graphite/](http://docs.grafana.org/datasources/graphite/)
[http://docs.grafana.org/datasources/graphite/](http://docs.grafana.org/datasources/graphite/)
Graphite 1.1 Release:
[https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/)

View File

@@ -17,6 +17,7 @@
},
"info": {
"description": "Graphite Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -24,6 +25,11 @@
"logos": {
"small": "img/graphite_logo.png",
"large": "img/graphite_logo.png"
}
},
"links": [
{"name": "Graphite", "url": "https://graphiteapp.org/"},
{"name": "Graphite 1.1 Release", "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"}
],
"version": "5.0.0"
}
}

View File

@@ -1,10 +1,8 @@
# InfluxDB Datasource - Native Plugin
Grafana ships with **built in** support for InfluxDB 0.9.
Grafana ships with **built in** support for InfluxDB (> 0.9.x).
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x. The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles them as different data sources.
This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API.
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and the latest InfluxDB release. The API and capabilities of latest (> 0.9.x) InfluxDB are completely different from InfluxDB 0.8.x which is why Grafana handles them as different data sources.
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://grafana.com/plugins/grafana-influxdb-08-datasource).

View File

@@ -13,6 +13,7 @@
},
"info": {
"description": "InfluxDB Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -20,6 +21,7 @@
"logos": {
"small": "img/influxdb_logo.svg",
"large": "img/influxdb_logo.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -1,3 +1,14 @@
# Grafana Fake Data Datasource - Native Plugin
# MySQL Data Source - Native Plugin
This is the built in Fake Data Datasource that is used before any datasources are set up in your Grafana installation. It means you can create a graph without any data and still get an idea of what it would look like.
Grafana ships with a built-in MySQL data source plugin that allow you to query any visualize data from a MySQL compatible database.
## Adding the data source
1. Open the side menu by clicking the Grafana icon in the top header.
2. In the side menu under the Dashboards link you should find a link named Data Sources.
3. Click the + Add data source button in the top header.
4. Select MySQL from the Type dropdown.
Read more about it here:
[http://docs.grafana.org/features/datasources/mysql/](http://docs.grafana.org/features/datasources/mysql/)

View File

@@ -4,6 +4,7 @@
"id": "mysql",
"info": {
"description": "MySQL Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,7 +12,8 @@
"logos": {
"small": "img/mysql_logo.svg",
"large": "img/mysql_logo.svg"
}
},
"version": "5.0.0"
},
"alerting": true,

View File

@@ -1,7 +1,7 @@
# OpenTSDB Datasource - Native Plugin
# OpenTSDB Data Source - Native Plugin
Grafana ships with **built in** support for OpenTSDB, a scalable, distributed time series database.
Read more about it here:
[http://docs.grafana.org/datasources/opentsdb/](http://docs.grafana.org/datasources/opentsdb/)
[http://docs.grafana.org/datasources/opentsdb/](http://docs.grafana.org/datasources/opentsdb/)

View File

@@ -9,6 +9,7 @@
"alerting": true,
"info": {
"description": "OpenTSDB Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -16,6 +17,7 @@
"logos": {
"small": "img/opentsdb_logo.png",
"large": "img/opentsdb_logo.png"
}
},
"version": "5.0.0"
}
}

View File

@@ -1,3 +1,12 @@
# Grafana PostgreSQL Datasource - Native Plugin
# Grafana PostgreSQL Data Source - Native Plugin
This is the built in PostgreSQL Datasource that is used to connect to PostgreSQL databases.
Grafana ships with a built-in PostgreSQL data source plugin that allows you to query and visualize data from a PostgreSQL compatible database.
## Adding the data source
1. Open the side menu by clicking the Grafana icon in the top header.
2. In the side menu under the Dashboards link you should find a link named Data Sources.
3. Click the + Add data source button in the top header.
4. Select PostgreSQL from the Type dropdown.
[http://docs.grafana.org/features/datasources/postgres/](http://docs.grafana.org/features/datasources/postgres/)

View File

@@ -4,6 +4,7 @@
"id": "postgres",
"info": {
"description": "PostgreSQL Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,7 +12,8 @@
"logos": {
"small": "img/postgresql_logo.svg",
"large": "img/postgresql_logo.svg"
}
},
"version": "5.0.0"
},
"alerting": true,

View File

@@ -1,4 +1,4 @@
# Prometheus Datasource - Native Plugin
# Prometheus Data Source - Native Plugin
Grafana ships with **built in** support for Prometheus, the open-source service monitoring system and time series database.

View File

@@ -18,6 +18,7 @@
},
"info": {
"description": "Prometheus Data Source for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -25,6 +26,10 @@
"logos": {
"small": "img/prometheus_logo.svg",
"large": "img/prometheus_logo.svg"
}
},
"links": [
{"name": "Prometheus", "url": "https://prometheus.io/"}
],
"version": "5.0.0"
}
}

View File

@@ -1 +1,9 @@
# Alert List Panel - Native plugin
This Alert List panel is **included** with Grafana.
The Alert List panel allows you to display alerts on a dashboard. The list can be configured to show either the current state of your alerts or recent alert state changes. You can read more about alerts [here](http://docs.grafana.org/alerting/rules).
Read more about it here:
[http://docs.grafana.org/features/panels/alertlist/](http://docs.grafana.org/features/panels/alertlist/)

View File

@@ -4,13 +4,15 @@
"id": "alertlist",
"info": {
"description": "Shows list of alerts and their current status",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
},
},
"logos": {
"small": "img/icn-singlestat-panel.svg",
"large": "img/icn-singlestat-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -4,6 +4,7 @@
"id": "dashlist",
"info": {
"description": "List of dynamic links to other dashboards",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,6 +12,7 @@
"logos": {
"small": "img/icn-dashlist-panel.svg",
"large": "img/icn-dashlist-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -151,7 +151,11 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
if (panel.legend.sort) {
seriesList = _.sortBy(seriesList, function(series) {
return series.stats[panel.legend.sort];
let sort = series.stats[panel.legend.sort];
if (sort === null) {
sort = -Infinity;
}
return sort;
});
if (panel.legend.sortDesc) {
seriesList = seriesList.reverse();

View File

@@ -4,6 +4,7 @@
"id": "graph",
"info": {
"description": "Graph Panel for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,7 +12,8 @@
"logos": {
"small": "img/icn-graph-panel.svg",
"large": "img/icn-graph-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -43,7 +43,7 @@
<div class="gf-form">
<label class="gf-form-label width-8">Point Radius</label>
<div class="gf-form-select-wrapper max-width-5">
<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.points"></select>
<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.points"></select>
</div>
</div>
</div>

View File

@@ -0,0 +1,7 @@
# Heatmap Panel - Native Plugin
The Heatmap panel allows you to view histograms over time and is **included** with Grafana.
Read more about it here:
[http://docs.grafana.org/features/panels/heatmap/](http://docs.grafana.org/features/panels/heatmap/)

View File

@@ -8,13 +8,18 @@ import { getColorScale, getOpacityScale } from './color_scale';
let module = angular.module('grafana.directives');
const LEGEND_HEIGHT_PX = 6;
const LEGEND_WIDTH_PX = 100;
const LEGEND_TICK_SIZE = 0;
const LEGEND_VALUE_MARGIN = 0;
/**
* Color legend for heatmap editor.
*/
module.directive('colorLegend', function() {
return {
restrict: 'E',
template: '<div class="heatmap-color-legend"><svg width="16.8rem" height="24px"></svg></div>',
template: '<div class="heatmap-color-legend"><svg width="16.5rem" height="24px"></svg></div>',
link: function(scope, elem, attrs) {
let ctrl = scope.ctrl;
let panel = scope.ctrl.panel;
@@ -50,7 +55,7 @@ module.directive('colorLegend', function() {
module.directive('heatmapLegend', function() {
return {
restrict: 'E',
template: '<div class="heatmap-color-legend"><svg width="100px" height="14px"></svg></div>',
template: `<div class="heatmap-color-legend"><svg width="${LEGEND_WIDTH_PX}px" height="${LEGEND_HEIGHT_PX}px"></svg></div>`,
link: function(scope, elem, attrs) {
let ctrl = scope.ctrl;
let panel = scope.ctrl.panel;
@@ -163,10 +168,10 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
let xAxis = d3
.axisBottom(legendValueScale)
.tickValues(ticks)
.tickSize(2);
.tickSize(LEGEND_TICK_SIZE);
let colorRect = legendElem.find(':first-child');
let posY = getSvgElemHeight(legendElem) + 2;
let posY = getSvgElemHeight(legendElem) + LEGEND_VALUE_MARGIN;
let posX = getSvgElemX(colorRect);
d3

View File

@@ -4,6 +4,7 @@
"id": "heatmap",
"info": {
"description": "Heatmap Panel for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,6 +12,11 @@
"logos": {
"small": "img/icn-heatmap-panel.svg",
"large": "img/icn-heatmap-panel.svg"
}
},
"links": [
{"name": "Brendan Gregg - Heatmaps", "url": "http://www.brendangregg.com/heatmaps.html"},
{"name": "Brendan Gregg - Latency Heatmaps", "url": " http://www.brendangregg.com/HeatMaps/latency.html"}
],
"version": "5.0.0"
}
}

View File

@@ -66,8 +66,7 @@ export default function link(scope, elem, attrs, ctrl) {
height = parseInt(height.replace('px', ''), 10);
}
height -= 5; // padding
height -= panel.title ? 24 : 9; // subtract panel title bar
height -= panel.legend.show ? 28 : 11; // bottom padding and space for legend
$heatmap.css('height', height + 'px');

View File

@@ -51,6 +51,9 @@ describe('grafanaHeatmap', function() {
colorScheme: 'interpolateOranges',
fillBackground: false,
},
legend: {
show: false,
},
xBucketSize: 1000,
xBucketNumber: null,
yBucketSize: 1,

View File

@@ -1,2 +1,3 @@
# Plugin List Panel - Native Plugin
The Plugin List plans shows the installed plugins for your Grafana instance and is **included** with Grafana. It is used on the default Home dashboard.

View File

@@ -4,6 +4,7 @@
"id": "pluginlist",
"info": {
"description": "Plugin List for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,6 +12,7 @@
"logos": {
"small": "img/icn-dashlist-panel.svg",
"large": "img/icn-dashlist-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -4,6 +4,7 @@
"id": "singlestat",
"info": {
"description": "Singlestat Panel for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,7 +12,8 @@
"logos": {
"small": "img/icn-singlestat-panel.svg",
"large": "img/icn-singlestat-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -6,4 +6,4 @@ The table panel is very flexible, supporting both multiple modes for time series
Check out the [Table Panel Showcase in the Grafana Playground](http://play.grafana.org/dashboard/db/table-panel-showcase) or read more about it here:
[http://docs.grafana.org/reference/table_panel/](http://docs.grafana.org/reference/table_panel/)
[http://docs.grafana.org/reference/table_panel/](http://docs.grafana.org/reference/table_panel/)

View File

@@ -4,6 +4,7 @@
"id": "table",
"info": {
"description": "Table Panel for Grafana",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
@@ -11,7 +12,8 @@
"logos": {
"small": "img/icn-table-panel.svg",
"large": "img/icn-table-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -11,7 +11,8 @@
"logos": {
"small": "img/icn-text-panel.svg",
"large": "img/icn-text-panel.svg"
}
},
"version": "5.0.0"
}
}

View File

@@ -14,7 +14,6 @@ export const AlertRule = types
stateAge: types.string,
info: types.optional(types.string, ''),
url: types.string,
canEdit: types.boolean,
})
.views(self => ({
get isPaused() {

View File

@@ -1,9 +1,8 @@
import { types } from 'mobx-state-tree';
import { types } from 'mobx-state-tree';
export const PermissionsStoreItem = types
.model('PermissionsStoreItem', {
dashboardId: types.optional(types.number, -1),
id: types.maybe(types.number),
permission: types.number,
permissionName: types.maybe(types.string),
role: types.maybe(types.string),

View File

@@ -72,7 +72,7 @@ $textShadow: none;
// gradients
$brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
$page-gradient: linear-gradient(-60deg, transparent 70%, $gray-7 98%);
$page-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
// Links
// -------------------------

View File

@@ -1,290 +1,254 @@
@import "base/font_awesome";
@import "base/grafana_icons";
@import 'font_awesome';
@import 'grafana_icons';
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0460-052f, U+20b4, U+2de0-2dff, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+1f00-1fff;
}
/* greek */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0370-03ff;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0102-0103, U+1ea0-1ef9, U+20ab;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f,
U+A720-A7FF;
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/roboto/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"),
url(../fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2) format("woff2");
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc,
U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2');
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0460-052f, U+20b4, U+2de0-2dff, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+1f00-1fff;
}
/* greek */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0370-03ff;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0102-0103, U+1ea0-1ef9, U+20ab;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2)
format("woff2");
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f,
U+A720-A7FF;
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"),
url(../fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2)
format("woff2");
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc,
U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/WxrXJa0C3KdtC7lMafG4dRTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/WxrXJa0C3KdtC7lMafG4dRTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0460-052f, U+20b4, U+2de0-2dff, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/OpXUqTo0UgQQhGj_SFdLWBTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/OpXUqTo0UgQQhGj_SFdLWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/1hZf02POANh32k2VkgEoUBTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/1hZf02POANh32k2VkgEoUBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+1f00-1fff;
}
/* greek */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/cDKhRaXnQTOVbaoxwdOr9xTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/cDKhRaXnQTOVbaoxwdOr9xTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0370-03ff;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/K23cxWVTrIFD6DJsEVi07RTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/K23cxWVTrIFD6DJsEVi07RTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0102-0103, U+1ea0-1ef9, U+20ab;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/vSzulfKSK0LLjjfeaxcREhTbgVql8nDJpwnrE27mub0.woff2)
format("woff2");
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f,
U+A720-A7FF;
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/vSzulfKSK0LLjjfeaxcREhTbgVql8nDJpwnrE27mub0.woff2) format('woff2');
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local("Roboto Italic"), local("Roboto-Italic"),
url(../fonts/roboto/vPcynSL0qHq_6dX7lKVByfesZW2xOQ-xsNqO47m55DA.woff2)
format("woff2");
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc,
U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
src: local('Roboto Italic'), local('Roboto-Italic'),
url(../fonts/roboto/vPcynSL0qHq_6dX7lKVByfesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0TTOQ_MqJVwkKsUn0wKzc2I.woff2)
format("woff2");
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0TTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');
unicode-range: U+0460-052f, U+20b4, U+2de0-2dff, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0TUj_cnvWIuuBMVgbX098Mw.woff2)
format("woff2");
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0TUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');
unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
}
/* greek-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0UbcKLIaa1LC45dFaAfauRA.woff2)
format("woff2");
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0UbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');
unicode-range: U+1f00-1fff;
}
/* greek */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0Wo_sUJ8uO4YLWRInS22T3Y.woff2)
format("woff2");
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0Wo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');
unicode-range: U+0370-03ff;
}
/* vietnamese */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0b6up8jxqWt8HVA3mDhkV_0.woff2)
format("woff2");
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0b6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');
unicode-range: U+0102-0103, U+1ea0-1ef9, U+20ab;
}
/* latin-ext */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0SYE0-AqJ3nfInTTiDXDjU4.woff2)
format("woff2");
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f,
U+A720-A7FF;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0SYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');
unicode-range: U+0100-024f, U+1-1eff, U+20a0-20ab, U+20ad-20cf, U+2c60-2c7f, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Roboto";
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local("Roboto Medium Italic"), local("Roboto-MediumItalic"),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0Y4P5ICox8Kq3LLUNMylGO4.woff2)
format("woff2");
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc,
U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url(../fonts/roboto/OLffGBTaF0XFOW1gnuHF0Y4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');
unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2212, U+2215;
}

View File

@@ -41,8 +41,8 @@
.theme-dark {
.react-grid-item > .react-resizable-handle::after {
border-right: 2px solid $gray-4;
border-bottom: 2px solid $gray-4;
border-right: 2px solid $gray-1;
border-bottom: 2px solid $gray-1;
}
}

View File

@@ -67,6 +67,11 @@
.modal-content {
padding: $spacer*2;
&--has-scroll {
max-height: calc(100vh - 400px);
position: relative;
}
}
// Remove bottom margin if need be

View File

@@ -16,6 +16,10 @@
padding-left: 0px;
}
.graph-legend-table {
width: auto;
}
.graph-legend-table .graph-legend-series {
display: table-row;
}
@@ -45,7 +49,7 @@
.graph-legend {
flex: 0 1 auto;
max-height: 30%;
margin: 0 $spacer;
margin: 0;
text-align: center;
padding-top: 6px;
position: relative;
@@ -70,19 +74,19 @@
font-size: 85%;
text-align: left;
&.current::before {
content: "Current: ";
content: 'Current: ';
}
&.max::before {
content: "Max: ";
content: 'Max: ';
}
&.min::before {
content: "Min: ";
content: 'Min: ';
}
&.total::before {
content: "Total: ";
content: 'Total: ';
}
&.avg::before {
content: "Avg: ";
content: 'Avg: ';
}
}
@@ -106,6 +110,15 @@
padding-left: 6px;
}
// fix for phantomjs
.body--phantomjs {
.graph-panel--legend-right {
.graph-legend-table {
display: table;
}
}
}
.graph-legend-table {
tbody {
display: block;
@@ -114,6 +127,7 @@
height: 100%;
padding-bottom: 1px;
padding-right: 5px;
padding-left: 5px;
}
.graph-legend-series {
@@ -124,7 +138,7 @@
float: none;
.graph-legend-alias::after {
content: "(right-y)";
content: '(right-y)';
padding: 0 5px;
color: $text-color-weak;
}
@@ -175,7 +189,7 @@
&.total,
&.avg {
&::before {
content: "";
content: '';
}
}
}

View File

@@ -1,3 +1,5 @@
$font-size-heatmap-tick: 11px;
.heatmap-canvas-wrapper {
// position: relative;
cursor: crosshair;
@@ -10,7 +12,7 @@
text {
fill: $text-color;
color: $text-color;
font-size: $font-size-sm;
font-size: $font-size-heatmap-tick;
}
line {
@@ -56,12 +58,12 @@
.heatmap-legend-wrapper {
@include clearfix();
margin: 0 $spacer;
padding-top: 10px;
padding-top: 4px;
svg {
width: 100%;
max-width: 300px;
height: 33px;
height: 18px;
float: left;
white-space: nowrap;
padding-left: 10px;
@@ -75,7 +77,7 @@
text {
fill: $text-color;
color: $text-color;
font-size: $font-size-sm;
font-size: $font-size-heatmap-tick;
}
line {