mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'develop' of github.com:grafana/grafana into develop
This commit is contained in:
@@ -110,18 +110,21 @@
|
||||
"angular-sanitize": "^1.6.6",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"brace": "^0.10.0",
|
||||
"classnames": "^2.2.5",
|
||||
"clipboard": "^1.7.1",
|
||||
"eventemitter3": "^2.0.2",
|
||||
"gridstack": "https://github.com/grafana/gridstack.js#grafana",
|
||||
"gemini-scrollbar": "https://github.com/grafana/gemini-scrollbar#grafana",
|
||||
"file-saver": "^1.3.3",
|
||||
"gemini-scrollbar": "https://github.com/grafana/gemini-scrollbar#grafana",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"moment": "^2.18.1",
|
||||
"mousetrap": "^1.6.0",
|
||||
"ngreact": "^0.4.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-grid-layout": "^0.16.0",
|
||||
"react-sizeme": "^2.3.6",
|
||||
"remarkable": "^1.7.1",
|
||||
"rxjs": "^5.4.3",
|
||||
"tether": "^1.4.0",
|
||||
|
||||
@@ -280,10 +280,12 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
|
||||
newpanel := simplejson.NewFromAny(map[string]interface{}{
|
||||
"type": "gettingstarted",
|
||||
"id": 123123,
|
||||
"gridPos": map[string]interface{}{
|
||||
"x": 0,
|
||||
"y": 3,
|
||||
"width": 12,
|
||||
"height": 4,
|
||||
"w": 12,
|
||||
"h": 4,
|
||||
},
|
||||
})
|
||||
|
||||
panels = append(panels, newpanel)
|
||||
|
||||
@@ -189,14 +189,13 @@ type DashboardSearchProjection struct {
|
||||
}
|
||||
|
||||
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
limit := query.Limit
|
||||
if limit == 0 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`
|
||||
SELECT
|
||||
dashboard.id,
|
||||
@@ -207,36 +206,69 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
dashboard.folder_id,
|
||||
folder.slug as folder_slug,
|
||||
folder.title as folder_title
|
||||
FROM (
|
||||
FROM `)
|
||||
|
||||
// add tags filter
|
||||
if len(query.Tags) > 0 {
|
||||
sql.WriteString(
|
||||
`(
|
||||
SELECT
|
||||
dashboard.id FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id
|
||||
`)
|
||||
|
||||
// add tags filter
|
||||
if len(query.Tags) > 0 {
|
||||
sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `)`)
|
||||
for _, tag := range query.Tags {
|
||||
params = append(params, tag)
|
||||
}
|
||||
}
|
||||
|
||||
// this ends the inner select (tag filtered part)
|
||||
sql.WriteString(`
|
||||
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
|
||||
ORDER BY dashboard.title ASC LIMIT ?) as ids`)
|
||||
params = append(params, len(query.Tags))
|
||||
params = append(params, limit)
|
||||
|
||||
sql.WriteString(`
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`)
|
||||
if query.IsStarred {
|
||||
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
|
||||
sql.WriteString(` WHERE dashboard.org_id=?`)
|
||||
sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `) AND `)
|
||||
for _, tag := range query.Tags {
|
||||
params = append(params, tag)
|
||||
}
|
||||
params = createSearchWhereClause(query, &sql, params)
|
||||
fmt.Printf("params2 %v", params)
|
||||
|
||||
// this ends the inner select (tag filtered part)
|
||||
sql.WriteString(`
|
||||
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
|
||||
LIMIT ?) as ids
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
`)
|
||||
|
||||
params = append(params, len(query.Tags))
|
||||
params = append(params, limit)
|
||||
} else {
|
||||
sql.WriteString(`( SELECT dashboard.id FROM dashboard `)
|
||||
if query.IsStarred {
|
||||
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
sql.WriteString(` WHERE `)
|
||||
params = createSearchWhereClause(query, &sql, params)
|
||||
|
||||
sql.WriteString(`
|
||||
LIMIT ?) as ids
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
`)
|
||||
params = append(params, limit)
|
||||
}
|
||||
|
||||
sql.WriteString(`
|
||||
LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`)
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 5000"))
|
||||
|
||||
var res []DashboardSearchProjection
|
||||
|
||||
err := x.Sql(sql.String(), params...).Find(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func createSearchWhereClause(query *search.FindPersistedDashboardsQuery, sql *bytes.Buffer, params []interface{}) []interface{} {
|
||||
sql.WriteString(` dashboard.org_id=?`)
|
||||
params = append(params, query.SignedInUser.OrgId)
|
||||
|
||||
if query.IsStarred {
|
||||
@@ -262,7 +294,8 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
d.has_acl = 1 and
|
||||
(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
|
||||
and d.org_id = ?
|
||||
))`
|
||||
)
|
||||
)`
|
||||
|
||||
sql.WriteString(allowedDashboardsSubQuery)
|
||||
params = append(params, query.SignedInUser.UserId, query.SignedInUser.UserId, query.SignedInUser.OrgId)
|
||||
@@ -286,16 +319,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
params = append(params, query.FolderId)
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
|
||||
|
||||
var res []DashboardSearchProjection
|
||||
|
||||
err := x.Sql(sql.String(), params...).Find(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return params
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
|
||||
@@ -3,6 +3,7 @@ package sqlstore
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -12,9 +13,10 @@ import (
|
||||
)
|
||||
|
||||
func TestDashboardDataAccess(t *testing.T) {
|
||||
var x *xorm.Engine
|
||||
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
x = InitTestDB(t)
|
||||
|
||||
Convey("Given saved dashboard", func() {
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
|
||||
@@ -11,11 +11,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) {
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
// x.ShowSQL()
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
@@ -28,6 +27,8 @@ func InitTestDB(t *testing.T) {
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
|
||||
@@ -19,7 +19,7 @@ function (_, $, coreModule) {
|
||||
});
|
||||
|
||||
var lastHideControlsVal;
|
||||
$scope.$watch('dashboard.hideControls', function() {
|
||||
$scope.$watch('ctrl.dashboard.hideControls', function() {
|
||||
if (!$scope.dashboard) {
|
||||
return;
|
||||
}
|
||||
@@ -31,7 +31,7 @@ function (_, $, coreModule) {
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('playlistSrv', function(newValue) {
|
||||
$scope.$watch('ctrl.playlistSrv', function(newValue) {
|
||||
elem.toggleClass('playlist-active', _.isObject(newValue));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ function ($, angular, coreModule, _) {
|
||||
}, 10);
|
||||
}
|
||||
|
||||
scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
|
||||
scope.$watch("ctrl.dashboardViewState.state.editview", function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
showEditorPane(null, {editview: newValue});
|
||||
} else if (oldValue) {
|
||||
|
||||
@@ -235,13 +235,13 @@ export class BackendSrv {
|
||||
|
||||
createDashboardFolder(name) {
|
||||
const dash = {
|
||||
schemaVersion: 16,
|
||||
title: name,
|
||||
editable: true,
|
||||
hideControls: true,
|
||||
rows: [
|
||||
{
|
||||
panels: [
|
||||
{
|
||||
id: 1,
|
||||
folderId: 0,
|
||||
headings: false,
|
||||
limit: 1000,
|
||||
@@ -249,28 +249,40 @@ export class BackendSrv {
|
||||
query: '',
|
||||
recent: false,
|
||||
search: true,
|
||||
span: 4,
|
||||
starred: false,
|
||||
tags: [],
|
||||
title: 'Dashboards in this folder',
|
||||
type: 'dashlist'
|
||||
type: 'dashlist',
|
||||
gridPos: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 4,
|
||||
h: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
onlyAlertsOnDashboard: true,
|
||||
span: 4,
|
||||
title: 'Alerts in this folder',
|
||||
type: 'alertlist'
|
||||
type: 'alertlist',
|
||||
gridPos: {
|
||||
x: 4,
|
||||
y: 0,
|
||||
w: 4,
|
||||
h: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
span: 4,
|
||||
id: 3,
|
||||
title: 'Permissions for this folder',
|
||||
type: 'permissionlist',
|
||||
folderId: 0
|
||||
folderId: 0,
|
||||
gridPos: {
|
||||
x: 8,
|
||||
y: 0,
|
||||
w: 4,
|
||||
h: 10
|
||||
}
|
||||
],
|
||||
showTitle: true,
|
||||
title: name,
|
||||
titleSize: 'h1'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -280,8 +292,8 @@ export class BackendSrv {
|
||||
return this.getDashboard('db', res.slug);
|
||||
})
|
||||
.then(res => {
|
||||
res.dashboard.rows[0].panels[0].folderId = res.dashboard.id;
|
||||
res.dashboard.rows[0].panels[2].folderId = res.dashboard.id;
|
||||
res.dashboard.panels[0].folderId = res.dashboard.id;
|
||||
res.dashboard.panels[2].folderId = res.dashboard.id;
|
||||
return this.saveDashboard(res.dashboard, {overwrite: false});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
@@ -8,20 +6,11 @@ import $ from 'jquery';
|
||||
import {DEFAULT_ANNOTATION_COLOR} from 'app/core/utils/colors';
|
||||
import {Emitter, contextSrv, appEvents} from 'app/core/core';
|
||||
import {DashboardRow} from './row/row_model';
|
||||
import {PanelModel} from './PanelModel';
|
||||
import sortByKeys from 'app/core/utils/sort_by_keys';
|
||||
|
||||
export interface Panel {
|
||||
id: number;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
type: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const CELL_HEIGHT = 30;
|
||||
export const CELL_VMARGIN = 15;
|
||||
export const CELL_VMARGIN = 10;
|
||||
|
||||
export class DashboardModel {
|
||||
id: any;
|
||||
@@ -50,7 +39,7 @@ export class DashboardModel {
|
||||
events: any;
|
||||
editMode: boolean;
|
||||
folderId: number;
|
||||
panels: Panel[];
|
||||
panels: PanelModel[];
|
||||
|
||||
constructor(data, meta?) {
|
||||
if (!data) {
|
||||
@@ -80,8 +69,7 @@ export class DashboardModel {
|
||||
this.links = data.links || [];
|
||||
this.gnetId = data.gnetId || null;
|
||||
this.folderId = data.folderId || null;
|
||||
this.panels = data.panels || [];
|
||||
this.rows = [];
|
||||
this.panels = _.map(data.panels || [], panelData => new PanelModel(panelData));
|
||||
|
||||
this.addBuiltInAnnotationQuery();
|
||||
this.initMeta(meta);
|
||||
@@ -134,32 +122,39 @@ export class DashboardModel {
|
||||
// temp remove stuff
|
||||
var events = this.events;
|
||||
var meta = this.meta;
|
||||
var rows = this.rows;
|
||||
var variables = this.templating.list;
|
||||
var panels = this.panels;
|
||||
|
||||
delete this.events;
|
||||
delete this.meta;
|
||||
delete this.panels;
|
||||
|
||||
// prepare save model
|
||||
this.rows = _.map(rows, row => row.getSaveModel());
|
||||
this.templating.list = _.map(variables, variable => variable.getSaveModel ? variable.getSaveModel() : variable);
|
||||
this.panels = _.map(panels, panel => panel.getSaveModel());
|
||||
|
||||
// make clone
|
||||
var copy = $.extend(true, {}, this);
|
||||
// sort clone
|
||||
copy = sortByKeys(copy);
|
||||
console.log(copy.panels);
|
||||
|
||||
// restore properties
|
||||
this.events = events;
|
||||
this.meta = meta;
|
||||
this.rows = rows;
|
||||
this.templating.list = variables;
|
||||
this.panels = panels;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
addEmptyRow() {
|
||||
this.rows.push(new DashboardRow({isNew: true}));
|
||||
setViewMode(panel: PanelModel, fullscreen: boolean, isEditing: boolean) {
|
||||
this.meta.fullscreen = fullscreen;
|
||||
this.meta.isEditing = isEditing && this.meta.canEdit;
|
||||
|
||||
panel.setViewMode(fullscreen, this.meta.isEditing);
|
||||
|
||||
this.events.emit('view-mode-changed', panel);
|
||||
}
|
||||
|
||||
private ensureListExist(data) {
|
||||
@@ -194,7 +189,8 @@ export class DashboardModel {
|
||||
|
||||
addPanel(panel) {
|
||||
panel.id = this.getNextPanelId();
|
||||
this.panels.push(panel);
|
||||
this.panels.unshift(new PanelModel(panel));
|
||||
this.events.emit('panel-added', panel);
|
||||
}
|
||||
|
||||
removePanel(panel, ask?) {
|
||||
@@ -296,6 +292,14 @@ export class DashboardModel {
|
||||
}
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
this.events.on(eventName, callback);
|
||||
}
|
||||
|
||||
off(eventName, callback?) {
|
||||
this.events.off(eventName, callback);
|
||||
}
|
||||
|
||||
cycleGraphTooltip() {
|
||||
this.graphTooltip = (this.graphTooltip + 1) % 3;
|
||||
}
|
||||
@@ -338,7 +342,7 @@ export class DashboardModel {
|
||||
var i, j, k;
|
||||
var oldVersion = this.schemaVersion;
|
||||
var panelUpgrades = [];
|
||||
this.schemaVersion = 15;
|
||||
this.schemaVersion = 16;
|
||||
|
||||
if (oldVersion === this.schemaVersion) {
|
||||
return;
|
||||
@@ -647,7 +651,7 @@ export class DashboardModel {
|
||||
this.graphTooltip = old.sharedCrosshair ? 1 : 0;
|
||||
}
|
||||
|
||||
if (oldVersion < 15) {
|
||||
if (oldVersion < 16) {
|
||||
this.upgradeToGridLayout(old);
|
||||
}
|
||||
|
||||
@@ -655,60 +659,59 @@ export class DashboardModel {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.rows.length; i++) {
|
||||
var row = this.rows[i];
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
for (j = 0; j < this.panels.length; j++) {
|
||||
for (k = 0; k < panelUpgrades.length; k++) {
|
||||
panelUpgrades[k].call(this, row.panels[j]);
|
||||
}
|
||||
panelUpgrades[k].call(this, this.panels[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upgradeToGridLayout(old) {
|
||||
let yPos = 0;
|
||||
let rowIds = 1000;
|
||||
//let rowIds = 1000;
|
||||
//
|
||||
|
||||
if (!old.rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let row of old.rows) {
|
||||
let xPos = 0;
|
||||
let height: any = row.height;
|
||||
let height: any = row.height || 250;
|
||||
|
||||
if (this.meta.keepRows) {
|
||||
this.panels.push({
|
||||
id: rowIds++,
|
||||
type: 'row',
|
||||
title: row.title,
|
||||
x: 0,
|
||||
y: yPos,
|
||||
height: 1,
|
||||
width: 12
|
||||
});
|
||||
|
||||
yPos += 1;
|
||||
}
|
||||
// if (this.meta.keepRows) {
|
||||
// this.panels.push({
|
||||
// id: rowIds++,
|
||||
// type: 'row',
|
||||
// title: row.title,
|
||||
// x: 0,
|
||||
// y: yPos,
|
||||
// height: 1,
|
||||
// width: 12
|
||||
// });
|
||||
//
|
||||
// yPos += 1;
|
||||
// }
|
||||
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height = Math.ceil(height / CELL_HEIGHT);
|
||||
const rowGridHeight = Math.ceil(height / CELL_HEIGHT);
|
||||
|
||||
for (let panel of row.panels) {
|
||||
// should wrap to next row?
|
||||
if (xPos + panel.span >= 12) {
|
||||
yPos += height;
|
||||
yPos += rowGridHeight;
|
||||
}
|
||||
|
||||
panel.x = xPos;
|
||||
panel.y = yPos;
|
||||
panel.width = panel.span;
|
||||
panel.height = height;
|
||||
panel.gridPos = { x: xPos, y: yPos, w: panel.span, h: rowGridHeight };
|
||||
|
||||
delete panel.span;
|
||||
|
||||
xPos += panel.width;
|
||||
xPos += rowGridHeight;
|
||||
|
||||
this.panels.push(panel);
|
||||
this.panels.push(new PanelModel(panel));
|
||||
}
|
||||
|
||||
yPos += height;
|
||||
74
public/app/features/dashboard/PanelModel.ts
Normal file
74
public/app/features/dashboard/PanelModel.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {Emitter} from 'app/core/core';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
const notPersistedProperties: {[str: string]: boolean} = {
|
||||
"events": true,
|
||||
"fullscreen": true,
|
||||
"isEditing": true,
|
||||
};
|
||||
|
||||
export class PanelModel {
|
||||
id: number;
|
||||
gridPos: GridPos;
|
||||
type: string;
|
||||
title: string;
|
||||
alert?: any;
|
||||
|
||||
// non persisted
|
||||
fullscreen: boolean;
|
||||
isEditing: boolean;
|
||||
events: Emitter;
|
||||
|
||||
constructor(model) {
|
||||
this.events = new Emitter();
|
||||
|
||||
// copy properties from persisted model
|
||||
for (var property in model) {
|
||||
this[property] = model[property];
|
||||
}
|
||||
}
|
||||
|
||||
getSaveModel() {
|
||||
const model: any = {};
|
||||
for (var property in this) {
|
||||
if (notPersistedProperties[property] || !this.hasOwnProperty(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
model[property] = this[property];
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
setViewMode(fullscreen: boolean, isEditing: boolean) {
|
||||
this.fullscreen = fullscreen;
|
||||
this.isEditing = isEditing;
|
||||
this.events.emit('panel-size-changed');
|
||||
}
|
||||
|
||||
updateGridPos(newPos: GridPos) {
|
||||
let sizeChanged = false;
|
||||
|
||||
if (this.gridPos.w !== newPos.w || this.gridPos.h !== newPos.h) {
|
||||
sizeChanged = true;
|
||||
}
|
||||
|
||||
this.gridPos.x = newPos.x;
|
||||
this.gridPos.y = newPos.y;
|
||||
this.gridPos.w = newPos.w;
|
||||
this.gridPos.h = newPos.h;
|
||||
|
||||
if (sizeChanged) {
|
||||
console.log('PanelModel sizeChanged event and render events fired');
|
||||
this.events.emit('panel-size-changed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ define([
|
||||
'./unsavedChangesSrv',
|
||||
'./unsaved_changes_modal',
|
||||
'./timepicker/timepicker',
|
||||
'./graphiteImportCtrl',
|
||||
'./impression_store',
|
||||
'./upload',
|
||||
'./import/dash_import',
|
||||
@@ -23,7 +22,9 @@ define([
|
||||
'./export_data/export_data_modal',
|
||||
'./ad_hoc_filters',
|
||||
'./repeat_option/repeat_option',
|
||||
'./dashgrid/dashgrid',
|
||||
'./dashgrid/DashboardGrid',
|
||||
'./dashgrid/PanelLoader',
|
||||
'./row/add_panel',
|
||||
'./acl/acl',
|
||||
'./folder_picker/picker',
|
||||
'./folder_modal/folder'
|
||||
|
||||
@@ -1,75 +1,84 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import config from 'app/core/config';
|
||||
import angular from 'angular';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {PanelContainer} from './dashgrid/PanelContainer';
|
||||
import {DashboardModel} from './DashboardModel';
|
||||
|
||||
export class DashboardCtrl {
|
||||
export class DashboardCtrl implements PanelContainer {
|
||||
dashboard: DashboardModel;
|
||||
dashboardViewState: any;
|
||||
loadedFallbackDashboard: boolean;
|
||||
editTab: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope,
|
||||
$rootScope,
|
||||
keybindingSrv,
|
||||
timeSrv,
|
||||
variableSrv,
|
||||
alertingSrv,
|
||||
dashboardSrv,
|
||||
unsavedChangesSrv,
|
||||
dynamicDashboardSrv,
|
||||
dashboardViewStateSrv,
|
||||
contextSrv,
|
||||
alertSrv,
|
||||
$timeout) {
|
||||
private $rootScope,
|
||||
private keybindingSrv,
|
||||
private timeSrv,
|
||||
private variableSrv,
|
||||
private alertingSrv,
|
||||
private dashboardSrv,
|
||||
private unsavedChangesSrv,
|
||||
private dynamicDashboardSrv,
|
||||
private dashboardViewStateSrv,
|
||||
private panelLoader) {
|
||||
// temp hack due to way dashboards are loaded
|
||||
// can't use controllerAs on route yet
|
||||
$scope.ctrl = this;
|
||||
|
||||
$scope.editor = { index: 0 };
|
||||
// TODO: break out settings view to separate view & controller
|
||||
this.editTab = 0;
|
||||
|
||||
var resizeEventTimeout;
|
||||
|
||||
$scope.setupDashboard = function(data) {
|
||||
try {
|
||||
$scope.setupDashboardInternal(data);
|
||||
} catch (err) {
|
||||
$scope.onInitFailed(err, 'Dashboard init failed', true);
|
||||
// funcs called from React component bindings and needs this binding
|
||||
this.getPanelContainer = this.getPanelContainer.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setupDashboardInternal = function(data) {
|
||||
var dashboard = dashboardSrv.create(data.dashboard, data.meta);
|
||||
dashboardSrv.setCurrent(dashboard);
|
||||
setupDashboard(data) {
|
||||
try {
|
||||
this.setupDashboardInternal(data);
|
||||
} catch (err) {
|
||||
this.onInitFailed(err, 'Dashboard init failed', true);
|
||||
}
|
||||
}
|
||||
|
||||
setupDashboardInternal(data) {
|
||||
const dashboard = this.dashboardSrv.create(data.dashboard, data.meta);
|
||||
this.dashboardSrv.setCurrent(dashboard);
|
||||
|
||||
// init services
|
||||
timeSrv.init(dashboard);
|
||||
alertingSrv.init(dashboard, data.alerts);
|
||||
this.timeSrv.init(dashboard);
|
||||
this.alertingSrv.init(dashboard, data.alerts);
|
||||
|
||||
// template values service needs to initialize completely before
|
||||
// the rest of the dashboard can load
|
||||
variableSrv.init(dashboard)
|
||||
this.variableSrv.init(dashboard)
|
||||
// template values failes are non fatal
|
||||
.catch($scope.onInitFailed.bind(this, 'Templating init failed', false))
|
||||
.catch(this.onInitFailed.bind(this, 'Templating init failed', false))
|
||||
// continue
|
||||
.finally(function() {
|
||||
dynamicDashboardSrv.init(dashboard);
|
||||
dynamicDashboardSrv.process();
|
||||
.finally(() => {
|
||||
this.dashboard = dashboard;
|
||||
|
||||
unsavedChangesSrv.init(dashboard, $scope);
|
||||
this.dynamicDashboardSrv.init(dashboard);
|
||||
this.dynamicDashboardSrv.process();
|
||||
|
||||
$scope.dashboard = dashboard;
|
||||
$scope.dashboardMeta = dashboard.meta;
|
||||
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
|
||||
this.unsavedChangesSrv.init(dashboard, this.$scope);
|
||||
|
||||
keybindingSrv.setupDashboardBindings($scope, dashboard);
|
||||
// TODO refactor ViewStateSrv
|
||||
this.$scope.dashboard = dashboard;
|
||||
this.dashboardViewState = this.dashboardViewStateSrv.create(this.$scope);
|
||||
|
||||
$scope.dashboard.updateSubmenuVisibility();
|
||||
$scope.setWindowTitleAndTheme();
|
||||
this.keybindingSrv.setupDashboardBindings(this.$scope, dashboard);
|
||||
|
||||
$scope.appEvent("dashboard-initialized", $scope.dashboard);
|
||||
this.dashboard.updateSubmenuVisibility();
|
||||
this.setWindowTitleAndTheme();
|
||||
|
||||
this.$scope.appEvent("dashboard-initialized", dashboard);
|
||||
})
|
||||
.catch($scope.onInitFailed.bind(this, 'Dashboard init failed', true));
|
||||
};
|
||||
.catch(this.onInitFailed.bind(this, 'Dashboard init failed', true));
|
||||
}
|
||||
|
||||
$scope.onInitFailed = function(msg, fatal, err) {
|
||||
onInitFailed(msg, fatal, err) {
|
||||
console.log(msg, err);
|
||||
|
||||
if (err.data && err.data.message) {
|
||||
@@ -78,66 +87,57 @@ export class DashboardCtrl {
|
||||
err = {message: err.toString()};
|
||||
}
|
||||
|
||||
$scope.appEvent("alert-error", [msg, err.message]);
|
||||
this.$scope.appEvent("alert-error", [msg, err.message]);
|
||||
|
||||
// protect against recursive fallbacks
|
||||
if (fatal && !$scope.loadedFallbackDashboard) {
|
||||
$scope.loadedFallbackDashboard = true;
|
||||
$scope.setupDashboard({dashboard: {title: 'Dashboard Init failed'}});
|
||||
if (fatal && !this.loadedFallbackDashboard) {
|
||||
this.loadedFallbackDashboard = true;
|
||||
this.setupDashboard({dashboard: {title: 'Dashboard Init failed'}});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.templateVariableUpdated = function() {
|
||||
dynamicDashboardSrv.process();
|
||||
};
|
||||
templateVariableUpdated() {
|
||||
this.dynamicDashboardSrv.process();
|
||||
}
|
||||
|
||||
$scope.setWindowTitleAndTheme = function() {
|
||||
window.document.title = config.window_title_prefix + $scope.dashboard.title;
|
||||
};
|
||||
setWindowTitleAndTheme() {
|
||||
window.document.title = config.window_title_prefix + this.dashboard.title;
|
||||
}
|
||||
|
||||
$scope.broadcastRefresh = function() {
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
$scope.addRowDefault = function() {
|
||||
$scope.dashboard.addEmptyRow();
|
||||
};
|
||||
|
||||
$scope.showJsonEditor = function(evt, options) {
|
||||
var editScope = $rootScope.$new();
|
||||
showJsonEditor(evt, options) {
|
||||
var editScope = this.$rootScope.$new();
|
||||
editScope.object = options.object;
|
||||
editScope.updateHandler = options.updateHandler;
|
||||
$scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope });
|
||||
};
|
||||
this.$scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope });
|
||||
}
|
||||
|
||||
$scope.registerWindowResizeEvent = function() {
|
||||
angular.element(window).bind('resize', function() {
|
||||
$timeout.cancel(resizeEventTimeout);
|
||||
resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200);
|
||||
});
|
||||
getDashboard() {
|
||||
return this.dashboard;
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
angular.element(window).unbind('resize');
|
||||
$scope.dashboard.destroy();
|
||||
});
|
||||
};
|
||||
getPanelLoader() {
|
||||
return this.panelLoader;
|
||||
}
|
||||
|
||||
$scope.timezoneChanged = function() {
|
||||
$rootScope.$broadcast("refresh");
|
||||
};
|
||||
timezoneChanged() {
|
||||
this.$rootScope.$broadcast("refresh");
|
||||
}
|
||||
|
||||
$scope.onFolderChange = function(folder) {
|
||||
$scope.dashboard.folderId = folder.id;
|
||||
$scope.dashboard.meta.folderId = folder.id;
|
||||
$scope.dashboard.meta.folderTitle= folder.title;
|
||||
};
|
||||
onFolderChange(folder) {
|
||||
this.dashboard.folderId = folder.id;
|
||||
this.dashboard.meta.folderId = folder.id;
|
||||
this.dashboard.meta.folderTitle= folder.title;
|
||||
}
|
||||
|
||||
getPanelContainer() {
|
||||
console.log('DashboardCtrl:getPanelContainer()');
|
||||
return this;
|
||||
}
|
||||
|
||||
init(dashboard) {
|
||||
this.$scope.onAppEvent('show-json-editor', this.$scope.showJsonEditor);
|
||||
this.$scope.onAppEvent('template-variable-value-updated', this.$scope.templateVariableUpdated);
|
||||
this.$scope.setupDashboard(dashboard);
|
||||
this.$scope.registerWindowResizeEvent();
|
||||
this.setupDashboard(dashboard);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {DashboardModel} from './model';
|
||||
import {DashboardModel} from './DashboardModel';
|
||||
|
||||
export class DashboardSrv {
|
||||
dash: any;
|
||||
|
||||
131
public/app/features/dashboard/dashgrid/DashboardGrid.tsx
Normal file
131
public/app/features/dashboard/dashgrid/DashboardGrid.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React from 'react';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import ReactGridLayout from 'react-grid-layout';
|
||||
import {CELL_HEIGHT, CELL_VMARGIN} from '../DashboardModel';
|
||||
import {DashboardPanel} from './DashboardPanel';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
import {PanelContainer} from './PanelContainer';
|
||||
import {PanelModel} from '../PanelModel';
|
||||
import classNames from 'classnames';
|
||||
import sizeMe from 'react-sizeme';
|
||||
|
||||
const COLUMN_COUNT = 12;
|
||||
|
||||
function GridWrapper({size, layout, onLayoutChange, children, onResize}) {
|
||||
if (size.width === 0) {
|
||||
console.log('size is zero!');
|
||||
}
|
||||
|
||||
const gridWidth = size.width > 0 ? size.width : 1200;
|
||||
|
||||
return (
|
||||
<ReactGridLayout
|
||||
width={gridWidth}
|
||||
className="layout"
|
||||
isDraggable={true}
|
||||
isResizable={true}
|
||||
measureBeforeMount={false}
|
||||
containerPadding={[0, 0]}
|
||||
useCSSTransforms={true}
|
||||
margin={[CELL_VMARGIN, CELL_VMARGIN]}
|
||||
cols={COLUMN_COUNT}
|
||||
rowHeight={CELL_HEIGHT}
|
||||
draggableHandle=".grid-drag-handle"
|
||||
layout={layout}
|
||||
onResize={onResize}
|
||||
onLayoutChange={onLayoutChange}>
|
||||
{children}
|
||||
</ReactGridLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const SizedReactLayoutGrid = sizeMe({monitorWidth: true})(GridWrapper);
|
||||
|
||||
export interface DashboardGridProps {
|
||||
getPanelContainer: () => PanelContainer;
|
||||
}
|
||||
|
||||
export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
gridToPanelMap: any;
|
||||
panelContainer: PanelContainer;
|
||||
dashboard: DashboardModel;
|
||||
panelMap: {[id: string]: PanelModel};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.panelContainer = this.props.getPanelContainer();
|
||||
this.onLayoutChange = this.onLayoutChange.bind(this);
|
||||
this.onResize = this.onResize.bind(this);
|
||||
|
||||
// subscribe to dashboard events
|
||||
this.dashboard = this.panelContainer.getDashboard();
|
||||
this.dashboard.on('panel-added', this.triggerForceUpdate.bind(this));
|
||||
this.dashboard.on('view-mode-changed', this.triggerForceUpdate.bind(this));
|
||||
}
|
||||
|
||||
buildLayout() {
|
||||
const layout = [];
|
||||
this.panelMap = {};
|
||||
|
||||
for (let panel of this.dashboard.panels) {
|
||||
let stringId = panel.id.toString();
|
||||
this.panelMap[stringId] = panel;
|
||||
|
||||
if (!panel.gridPos) {
|
||||
console.log('panel without gridpos');
|
||||
continue;
|
||||
}
|
||||
|
||||
layout.push({
|
||||
i: stringId,
|
||||
x: panel.gridPos.x,
|
||||
y: panel.gridPos.y,
|
||||
w: panel.gridPos.w,
|
||||
h: panel.gridPos.h,
|
||||
});
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
onLayoutChange(newLayout) {
|
||||
for (const newPos of newLayout) {
|
||||
this.panelMap[newPos.i].updateGridPos(newPos);
|
||||
}
|
||||
}
|
||||
|
||||
triggerForceUpdate() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
onResize(layout, oldItem, newItem) {
|
||||
this.panelMap[newItem.i].updateGridPos(newItem);
|
||||
}
|
||||
|
||||
renderPanels() {
|
||||
const panelElements = [];
|
||||
|
||||
for (let panel of this.dashboard.panels) {
|
||||
const panelClasses = classNames({panel: true, 'panel--fullscreen': panel.fullscreen});
|
||||
panelElements.push(
|
||||
<div key={panel.id.toString()} className={panelClasses}>
|
||||
<DashboardPanel panel={panel} getPanelContainer={this.props.getPanelContainer} />
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
return panelElements;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SizedReactLayoutGrid layout={this.buildLayout()} onLayoutChange={this.onLayoutChange} onResize={this.onResize}>
|
||||
{this.renderPanels()}
|
||||
</SizedReactLayoutGrid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.directive('dashboardGrid', function(reactDirective) {
|
||||
return reactDirective(DashboardGrid, [['getPanelContainer', {watchDepth: 'reference', wrapApply: false}]]);
|
||||
});
|
||||
40
public/app/features/dashboard/dashgrid/DashboardPanel.tsx
Normal file
40
public/app/features/dashboard/dashgrid/DashboardPanel.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import {PanelModel} from '../PanelModel';
|
||||
import {PanelContainer} from './PanelContainer';
|
||||
import {AttachedPanel} from './PanelLoader';
|
||||
|
||||
export interface DashboardPanelProps {
|
||||
panel: PanelModel;
|
||||
getPanelContainer: () => PanelContainer;
|
||||
}
|
||||
|
||||
export class DashboardPanel extends React.Component<DashboardPanelProps, any> {
|
||||
element: any;
|
||||
attachedPanel: AttachedPanel;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const panelContainer = this.props.getPanelContainer();
|
||||
const dashboard = panelContainer.getDashboard();
|
||||
const loader = panelContainer.getPanelLoader();
|
||||
|
||||
this.attachedPanel = loader.load(this.element, this.props.panel, dashboard);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.attachedPanel) {
|
||||
this.attachedPanel.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div ref={element => this.element = element} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
public/app/features/dashboard/dashgrid/PanelContainer.ts
Normal file
8
public/app/features/dashboard/dashgrid/PanelContainer.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
import {PanelLoader} from './PanelLoader';
|
||||
|
||||
export interface PanelContainer {
|
||||
getPanelLoader(): PanelLoader;
|
||||
getDashboard(): DashboardModel;
|
||||
}
|
||||
|
||||
34
public/app/features/dashboard/dashgrid/PanelLoader.ts
Normal file
34
public/app/features/dashboard/dashgrid/PanelLoader.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import angular from 'angular';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
export interface AttachedPanel {
|
||||
destroy();
|
||||
}
|
||||
|
||||
export class PanelLoader {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $compile, private $rootScope) {
|
||||
}
|
||||
|
||||
load(elem, panel, dashboard): AttachedPanel {
|
||||
var template = '<plugin-component type="panel"></plugin-component>';
|
||||
var panelScope = this.$rootScope.$new();
|
||||
panelScope.panel = panel;
|
||||
panelScope.dashboard = dashboard;
|
||||
|
||||
const compiledElem = this.$compile(template)(panelScope);
|
||||
const rootNode = angular.element(elem);
|
||||
rootNode.append(compiledElem);
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
console.log('AttachedPanel:Destroy, id' + panel.id);
|
||||
panelScope.$destroy();
|
||||
compiledElem.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('panelLoader', PanelLoader);
|
||||
@@ -1,211 +0,0 @@
|
||||
// ///<reference path="../../../headers/common.d.ts" />
|
||||
//
|
||||
// import coreModule from 'app/core/core_module';
|
||||
// import {CELL_HEIGHT, CELL_VMARGIN} from '../model';
|
||||
//
|
||||
// import 'jquery-ui';
|
||||
// import 'gridstack/dist/jquery.jQueryUI';
|
||||
// import 'gridstack';
|
||||
//
|
||||
// const template = `
|
||||
// <div class="grid-stack">
|
||||
// <dash-grid-item ng-repeat="panel in ctrl.dashboard.panels track by panel.id"
|
||||
// class="grid-stack-item"
|
||||
// grid-ctrl="ctrl"
|
||||
// panel="panel">
|
||||
// <plugin-component type="panel" class="grid-stack-item-content">
|
||||
// </plugin-component>
|
||||
// </dash-grid-item>
|
||||
// </div>
|
||||
// `;
|
||||
//
|
||||
// var rowIndex = 0;
|
||||
//
|
||||
// export class GridCtrl {
|
||||
// options: any;
|
||||
// dashboard: any;
|
||||
// panels: any;
|
||||
// gridstack: any;
|
||||
// gridElem: any;
|
||||
// isInitialized: boolean;
|
||||
// isDestroyed: boolean;
|
||||
// index: number;
|
||||
// changeRenderPromise: any;
|
||||
//
|
||||
// #<{(|* @ngInject |)}>#
|
||||
// constructor(private $scope, private $element, private $timeout) {
|
||||
// console.log(this.dashboard);
|
||||
// this.index = rowIndex;
|
||||
// rowIndex += 1;
|
||||
// }
|
||||
//
|
||||
// init() {
|
||||
// this.gridElem = this.$element.find('.grid-stack');
|
||||
//
|
||||
// this.gridstack = this.gridElem.gridstack({
|
||||
// animate: true,
|
||||
// cellHeight: CELL_HEIGHT,
|
||||
// verticalMargin: CELL_VMARGIN,
|
||||
// acceptWidgets: '.grid-stack-item',
|
||||
// handle: '.grid-drag-handle'
|
||||
// }).data('gridstack');
|
||||
//
|
||||
// this.isInitialized = true;
|
||||
//
|
||||
// this.gridElem.on('added', (e, items) => {
|
||||
// for (let item of items) {
|
||||
// this.onGridStackItemAdded(item);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// this.gridElem.on('removed', (e, items) => {
|
||||
// for (let item of items) {
|
||||
// this.onGridStackItemRemoved(item);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// this.gridElem.on('change', (e, items) => {
|
||||
// this.$timeout(() => this.onGridStackItemsChanged(items), 50);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// onGridStackItemAdded(item) {
|
||||
// console.log('row: ' + this.index + ' item added', item);
|
||||
// }
|
||||
//
|
||||
// onGridStackItemRemoved(item) {
|
||||
// console.log('row: ' + this.index + ' item removed', item.id, item);
|
||||
// }
|
||||
//
|
||||
// onGridStackItemsChanged(items) {
|
||||
// console.log('onGridStackItemsChanged');
|
||||
//
|
||||
// for (let item of items) {
|
||||
// // find panel
|
||||
// var panel = this.dashboard.getPanelById(parseInt(item.id));
|
||||
//
|
||||
// if (!panel) {
|
||||
// console.log('item change but no panel found for item', item);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // update panel model position
|
||||
// panel.x = item.x;
|
||||
// panel.y = item.y;
|
||||
// panel.width = item.width;
|
||||
// panel.height = item.height;
|
||||
//
|
||||
// console.log('updating panel: ' + panel.id + ' x: ' + panel.x + ' y: ' + panel.y);
|
||||
// }
|
||||
//
|
||||
// this.dashboard.panels.sort(function (a, b) {
|
||||
// let aScore = a.x + (a.y * 12);
|
||||
// let bScore = b.x + (b.y * 12);
|
||||
// if (aScore < bScore) { return -1; }
|
||||
// if (aScore > bScore) { return 1; }
|
||||
// return 0;
|
||||
// });
|
||||
//
|
||||
// if (this.changeRenderPromise) {
|
||||
// this.$timeout.cancel(this.changeRenderPromise);
|
||||
// }
|
||||
//
|
||||
// this.changeRenderPromise = this.$timeout(() => {
|
||||
// console.log('broadcasting render');
|
||||
// this.$scope.$broadcast('render');
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// destroy() {
|
||||
// this.gridstack.destroy();
|
||||
// this.gridstack = null;
|
||||
// this.isDestroyed = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #<{(|* @ngInject *|)}>#
|
||||
// export function dashGrid($timeout) {
|
||||
// return {
|
||||
// restrict: 'E',
|
||||
// template: template,
|
||||
// controller: GridCtrl,
|
||||
// bindToController: true,
|
||||
// controllerAs: 'ctrl',
|
||||
// scope: {
|
||||
// dashboard: "=",
|
||||
// },
|
||||
// link: function(scope, elem, attrs, ctrl) {
|
||||
// $timeout(function() {
|
||||
// ctrl.init();
|
||||
// });
|
||||
//
|
||||
// scope.$on('$destroy', () => {
|
||||
// ctrl.destroy();
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// #<{(|* @ngInject *|)}>#
|
||||
// export function dashGridItem($timeout, $rootScope) {
|
||||
// return {
|
||||
// restrict: "E",
|
||||
// scope: {
|
||||
// panel: '=',
|
||||
// gridCtrl: '='
|
||||
// },
|
||||
// link: function (scope, element, attrs) {
|
||||
// let gridCtrl = scope.gridCtrl;
|
||||
// let panel = scope.panel;
|
||||
// let gridStackNode = null;
|
||||
//
|
||||
// element.attr({
|
||||
// 'data-gs-id': panel.id,
|
||||
// 'data-gs-x': panel.x,
|
||||
// 'data-gs-y': panel.y,
|
||||
// 'data-gs-width': panel.width,
|
||||
// 'data-gs-height': panel.height,
|
||||
// 'data-gs-no-resize': panel.type === 'row',
|
||||
// });
|
||||
//
|
||||
// $rootScope.onAppEvent('panel-fullscreen-exit', (evt, payload) => {
|
||||
// if (panel.id !== payload.panelId) {
|
||||
// return;
|
||||
// }
|
||||
// gridCtrl.gridstack.locked(element, false);
|
||||
// element.removeClass('panel-fullscreen');
|
||||
// }, scope);
|
||||
//
|
||||
// $rootScope.onAppEvent('panel-fullscreen-enter', (evt, payload) => {
|
||||
// if (panel.id !== payload.panelId) {
|
||||
// return;
|
||||
// }
|
||||
// element.addClass('panel-fullscreen');
|
||||
// }, scope);
|
||||
//
|
||||
// scope.$on('$destroy', () => {
|
||||
// console.log('grid-item scope $destroy');
|
||||
// if (gridCtrl.isDestroyed) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (gridStackNode) {
|
||||
// console.log('grid-item scope $destroy removeWidget');
|
||||
// gridStackNode._grid.removeWidget(element);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// if (gridCtrl.isInitialized) {
|
||||
// gridCtrl.gridstack.makeWidget(element);
|
||||
// gridStackNode = element.data('_gridstack_node');
|
||||
// } else {
|
||||
// setTimeout(function() {
|
||||
// gridStackNode = element.data('_gridstack_node');
|
||||
// }, 500);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// coreModule.directive('dashGrid', dashGrid);
|
||||
// coreModule.directive('dashGridItem', dashGridItem);
|
||||
@@ -1,232 +0,0 @@
|
||||
Skip to content
|
||||
This repository
|
||||
Search
|
||||
Pull requests
|
||||
Issues
|
||||
Marketplace
|
||||
Gist
|
||||
@torkelo
|
||||
Sign out
|
||||
Unwatch 946
|
||||
Unstar 17,021
|
||||
Fork 2,862 grafana/grafana
|
||||
Code Issues 1,079 Pull requests 46 Projects 1 Wiki Settings Insights
|
||||
Branch: gridstack Find file Copy pathgrafana/public/app/core/components/dashgrid/dashgrid.ts
|
||||
a6bbcb8 on Jun 13
|
||||
@torkelo torkelo ux: gridstack poc
|
||||
1 contributor
|
||||
RawBlameHistory
|
||||
213 lines (181 sloc) 5.45 KB
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../../core_module';
|
||||
|
||||
import 'jquery-ui';
|
||||
import 'gridstack';
|
||||
import 'gridstack.jquery-ui';
|
||||
|
||||
const template = `
|
||||
<div gridstack gridstack-handler="ctrl.gridstack" class="grid-stack"
|
||||
options="ctrl.options"
|
||||
on-change="ctrl.onChange(event,items)"
|
||||
on-drag-start="ctrl.onDragStart(event,ui)"
|
||||
on-drag-stop="ctrl.onDragStop(event, ui)"
|
||||
on-resize-start="ctrl.onResizeStart(event, ui)"
|
||||
on-resize-stop="ctrl.onResizeStop(event, ui)">
|
||||
<div gridstack-item ng-repeat="panel in ctrl.panels"
|
||||
class="grid-stack-item"
|
||||
gs-item-id="panel.id"
|
||||
gs-item-x="panel.x"
|
||||
gs-item-y="panel.y"
|
||||
gs-item-width="panel.width"
|
||||
gs-item-height="panel.height"
|
||||
gs-item-autopos="1"
|
||||
on-item-added="ctrl.onItemAdded(item)"
|
||||
on-item-removed="ctrl.onItemRemoved(item)">
|
||||
<plugin-component type="panel" class="panel-margin grid-stack-item-content">
|
||||
</plugin-component>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export class DashGridCtrl {
|
||||
options: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope) {
|
||||
this.options = {
|
||||
animate: true,
|
||||
};
|
||||
}
|
||||
|
||||
onResizeStop() {
|
||||
this.$rootScope.$broadcast('render');
|
||||
}
|
||||
}
|
||||
|
||||
export function dashGrid($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
controller: DashGridCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: {
|
||||
dashboard: "="
|
||||
},
|
||||
link: function(scope, elem, attrs, ctrl) {
|
||||
|
||||
ctrl.panels = [];
|
||||
ctrl.dashboard.forEachPanel((panel, panelIndex, row, rowIndex) => {
|
||||
panel.width = 4;
|
||||
panel.height = 4;
|
||||
panel.x = panelIndex * 4;
|
||||
panel.y = rowIndex * 4;
|
||||
ctrl.panels.push(panel);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
coreModule.controller('GridstackController', ['$scope', function($scope) {
|
||||
|
||||
var gridstack = null;
|
||||
|
||||
this.init = function(element, options) {
|
||||
gridstack = element.gridstack(options).data('gridstack');
|
||||
return gridstack;
|
||||
};
|
||||
|
||||
this.removeItem = function(element) {
|
||||
if (gridstack) {
|
||||
return gridstack.removeWidget(element, false);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.addItem = function(element) {
|
||||
if (gridstack) {
|
||||
gridstack.makeWidget(element);
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
/** @ngInject */
|
||||
coreModule.directive('gridstack', ['$timeout', function($timeout) {
|
||||
return {
|
||||
restrict: "A",
|
||||
controller: 'GridstackController',
|
||||
scope: {
|
||||
onChange: '&',
|
||||
onDragStart: '&',
|
||||
onDragStop: '&',
|
||||
onResizeStart: '&',
|
||||
onResizeStop: '&',
|
||||
gridstackHandler: '=',
|
||||
options: '='
|
||||
},
|
||||
link: function (scope, element, attrs, controller, ngModel) {
|
||||
|
||||
var gridstack = controller.init(element, scope.options);
|
||||
scope.gridstackHandler = gridstack;
|
||||
|
||||
element.on('change', function (e, items) {
|
||||
$timeout(function() {
|
||||
scope.$apply();
|
||||
scope.onChange({event: e, items: items});
|
||||
});
|
||||
});
|
||||
|
||||
element.on('dragstart', function(e, ui) {
|
||||
scope.onDragStart({event: e, ui: ui});
|
||||
});
|
||||
|
||||
element.on('dragstop', function(e, ui) {
|
||||
$timeout(function() {
|
||||
scope.$apply();
|
||||
scope.onDragStop({event: e, ui: ui});
|
||||
});
|
||||
});
|
||||
|
||||
element.on('resizestart', function(e, ui) {
|
||||
scope.onResizeStart({event: e, ui: ui});
|
||||
});
|
||||
|
||||
element.on('resizestop', function(e, ui) {
|
||||
$timeout(function() {
|
||||
scope.$apply();
|
||||
scope.onResizeStop({event: e, ui: ui});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
/** @ngInject */
|
||||
coreModule.directive('gridstackItem', ['$timeout', function($timeout) {
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
controller: 'GridstackController',
|
||||
require: '^gridstack',
|
||||
scope: {
|
||||
gridstackItem: '=',
|
||||
onItemAdded: '&',
|
||||
onItemRemoved: '&',
|
||||
gsItemId: '=',
|
||||
gsItemX: '=',
|
||||
gsItemY: '=',
|
||||
gsItemWidth: '=',
|
||||
gsItemHeight: '=',
|
||||
gsItemAutopos: '='
|
||||
},
|
||||
link: function (scope, element, attrs, controller) {
|
||||
$(element).attr('data-gs-id', scope.gsItemId);
|
||||
$(element).attr('data-gs-x', scope.gsItemX);
|
||||
$(element).attr('data-gs-y', scope.gsItemY);
|
||||
$(element).attr('data-gs-width', scope.gsItemWidth);
|
||||
$(element).attr('data-gs-height', scope.gsItemHeight);
|
||||
$(element).attr('data-gs-auto-position', scope.gsItemAutopos);
|
||||
var widget = controller.addItem(element);
|
||||
var item = element.data('_gridstack_node');
|
||||
$timeout(function() {
|
||||
scope.onItemAdded({item: item});
|
||||
});
|
||||
scope.$watch(function () { return $(element).attr('data-gs-id'); }, function (val) {
|
||||
scope.gsItemId = val;
|
||||
});
|
||||
scope.$watch(function(){ return $(element).attr('data-gs-x'); }, function(val) {
|
||||
scope.gsItemX = val;
|
||||
});
|
||||
|
||||
scope.$watch(function(){ return $(element).attr('data-gs-y'); }, function(val) {
|
||||
scope.gsItemY = val;
|
||||
});
|
||||
|
||||
scope.$watch(function(){ return $(element).attr('data-gs-width'); }, function(val) {
|
||||
scope.gsItemWidth = val;
|
||||
});
|
||||
|
||||
scope.$watch(function(){ return $(element).attr('data-gs-height'); }, function(val) {
|
||||
scope.gsItemHeight = val;
|
||||
});
|
||||
|
||||
element.bind('$destroy', function() {
|
||||
var item = element.data('_gridstack_node');
|
||||
scope.onItemRemoved({item: item});
|
||||
controller.removeItem(element);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
coreModule.directive('dashGrid', dashGrid);
|
||||
Contact GitHub API Training Shop Blog About
|
||||
© 2017 GitHub, Inc. Terms Privacy Security Status Help
|
||||
@@ -70,7 +70,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li class="navbar-mini-btn-wrapper" ng-show="::ctrl.dashboard.meta.canSave">
|
||||
<button class="btn btn-secondary btn-mini" ng-click="ctrl.openEditView('add-panel')">
|
||||
<button class="btn btn-secondary btn-mini" ng-click="ctrl.addPanel()">
|
||||
<i class="fa fa-plus-circle"></i> Add Panel
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -4,7 +4,7 @@ import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import angular from 'angular';
|
||||
import {appEvents, NavModel} from 'app/core/core';
|
||||
import {DashboardModel} from '../model';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
|
||||
export class DashNavCtrl {
|
||||
dashboard: DashboardModel;
|
||||
@@ -92,16 +92,16 @@ export class DashNavCtrl {
|
||||
}
|
||||
|
||||
deleteDashboard() {
|
||||
var confirmText = "";
|
||||
var confirmText = '';
|
||||
var text2 = this.dashboard.title;
|
||||
var alerts = this.dashboard.rows.reduce((memo, row) => {
|
||||
memo += row.panels.filter(panel => panel.alert).length;
|
||||
return memo;
|
||||
}, 0);
|
||||
|
||||
const alerts = _.sumBy(this.dashboard.panels, panel => {
|
||||
return panel.alert ? 1 : 0;
|
||||
});
|
||||
|
||||
if (alerts > 0) {
|
||||
confirmText = 'DELETE';
|
||||
text2 = `This dashboard contains ${alerts} alerts. Deleting this dashboad will also delete those alerts`;
|
||||
text2 = `This dashboard contains ${alerts} alerts. Deleting this dashboard will also delete those alerts`;
|
||||
}
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
@@ -145,6 +145,14 @@ export class DashNavCtrl {
|
||||
this.$rootScope.appEvent('show-dash-search');
|
||||
}
|
||||
|
||||
addPanel() {
|
||||
this.dashboard.addPanel({
|
||||
type: 'graph',
|
||||
gridPos: {x: 0, y: 0, w: 6, h: 5},
|
||||
title: 'New Graph',
|
||||
});
|
||||
}
|
||||
|
||||
navItemClicked(navItem, evt) {
|
||||
if (navItem.clickHandler) {
|
||||
navItem.clickHandler();
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'app/core/utils/kbn'
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GraphiteImportCtrl', function($scope, datasourceSrv, dashboardSrv, $location) {
|
||||
$scope.options = {};
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.datasources = [];
|
||||
_.each(datasourceSrv.getAll(), function(ds) {
|
||||
if (ds.type === 'graphite') {
|
||||
$scope.options.sourceName = ds.name;
|
||||
$scope.datasources.push(ds.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.listAll = function() {
|
||||
datasourceSrv.get($scope.options.sourceName).then(function(datasource) {
|
||||
$scope.datasource = datasource;
|
||||
$scope.datasource.listDashboards('').then(function(results) {
|
||||
$scope.dashboards = results;
|
||||
}, function(err) {
|
||||
var message = err.message || err.statusText || 'Error';
|
||||
$scope.appEvent('alert-error', ['Failed to load dashboard list from graphite', message]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.import = function(dashName) {
|
||||
$scope.datasource.loadDashboard(dashName).then(function(results) {
|
||||
if (!results.data || !results.data.state) {
|
||||
throw { message: 'no dashboard state received from graphite' };
|
||||
}
|
||||
|
||||
graphiteToGrafanaTranslator(results.data.state, $scope.datasource.name);
|
||||
}, function(err) {
|
||||
var message = err.message || err.statusText || 'Error';
|
||||
$scope.appEvent('alert-error', ['Failed to load dashboard from graphite', message]);
|
||||
});
|
||||
};
|
||||
|
||||
function graphiteToGrafanaTranslator(state, datasource) {
|
||||
var graphsPerRow = 2;
|
||||
var rowHeight = 300;
|
||||
var rowTemplate;
|
||||
var currentRow;
|
||||
var panel;
|
||||
|
||||
rowTemplate = {
|
||||
title: '',
|
||||
panels: [],
|
||||
height: rowHeight
|
||||
};
|
||||
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
|
||||
var newDashboard = dashboardSrv.create({});
|
||||
newDashboard.rows = [];
|
||||
newDashboard.title = state.name;
|
||||
newDashboard.rows.push(currentRow);
|
||||
|
||||
_.each(state.graphs, function(graph, index) {
|
||||
if (currentRow.panels.length === graphsPerRow) {
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
newDashboard.rows.push(currentRow);
|
||||
}
|
||||
|
||||
panel = {
|
||||
type: 'graph',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: [],
|
||||
datasource: datasource,
|
||||
id: index + 1
|
||||
};
|
||||
|
||||
_.each(graph[1].target, function(target) {
|
||||
panel.targets.push({ target: target });
|
||||
});
|
||||
|
||||
currentRow.panels.push(panel);
|
||||
});
|
||||
|
||||
window.grafanaImportDashboard = newDashboard;
|
||||
$location.path('/dashboard-import/' + kbn.slugifyForUrl(newDashboard.title));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
|
||||
import {DashboardModel} from '../model';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
import {HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv} from './history_srv';
|
||||
|
||||
export class HistoryListCtrl {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {DashboardModel} from '../model';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
|
||||
export interface HistoryListOpts {
|
||||
limit: number;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<ul class="gf-tabs">
|
||||
<li class="gf-tabs-item" ng-repeat="tab in ::['General', 'Links', 'Time picker']">
|
||||
<a class="gf-tabs-link" ng-click="editor.index = $index" ng-class="{active: editor.index === $index}">
|
||||
<a class="gf-tabs-link" ng-click="ctrl.editTab = $index" ng-class="{active: ctrl.editTab === $index}">
|
||||
{{::tab}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -17,30 +17,30 @@
|
||||
</div>
|
||||
|
||||
<div class="tabbed-view-body">
|
||||
<div ng-if="editor.index == 0">
|
||||
<div ng-if="ctrl.editTab == 0">
|
||||
|
||||
<div class="gf-form-group section">
|
||||
<h5 class="section-heading">Details</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Name</label>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='dashboard.title'></input>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Description</label>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='dashboard.description'></input>
|
||||
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.description'></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">
|
||||
Tags
|
||||
<info-popover mode="right-normal">Press enter to add a tag</info-popover>
|
||||
</label>
|
||||
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
<bootstrap-tagsinput ng-model="ctrl.dashboard.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
</div>
|
||||
|
||||
<folder-picker ng-if="!dashboardMeta.isFolder"
|
||||
initial-title="dashboardMeta.folderTitle"
|
||||
on-change="onFolderChange($folder)"
|
||||
<folder-picker ng-if="!ctrl.dashboard.meta.isFolder"
|
||||
initial-title="ctrl.dashboard.meta.folderTitle"
|
||||
on-change="ctrl.onFolderChange($folder)"
|
||||
label-class="width-7">
|
||||
</folder-picker>
|
||||
</div>
|
||||
@@ -51,19 +51,19 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-11">Timezone</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f.value as f.text for f in [{value: '', text: 'Default'}, {value: 'browser', text: 'Local browser time'},{value: 'utc', text: 'UTC'}]" ng-change="timezoneChanged()"></select>
|
||||
<select ng-model="ctrl.dashboard.timezone" class='gf-form-input' ng-options="f.value as f.text for f in [{value: '', text: 'Default'}, {value: 'browser', text: 'Local browser time'},{value: 'utc', text: 'UTC'}]" ng-change="timezoneChanged()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Editable"
|
||||
tooltip="Uncheck, then save and reload to disable all dashboard editing"
|
||||
checked="dashboard.editable"
|
||||
checked="ctrl.dashboard.editable"
|
||||
label-class="width-11">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form"
|
||||
label="Hide Controls"
|
||||
tooltip="Hide row controls. Shortcut: CTRL+H or CMD+H"
|
||||
checked="dashboard.hideControls"
|
||||
checked="ctrl.dashboard.hideControls"
|
||||
label-class="width-11">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
@@ -79,7 +79,7 @@
|
||||
</info-popover>
|
||||
</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="dashboard.graphTooltip" class='gf-form-input' ng-options="f.value as f.text for f in [{value: 0, text: 'Default'}, {value: 1, text: 'Shared crosshair'},{value: 2, text: 'Shared Tooltip'}]"></select>
|
||||
<select ng-model="ctrl.dashboard.graphTooltip" class='gf-form-input' ng-options="f.value as f.text for f in [{value: 0, text: 'Default'}, {value: 1, text: 'Shared crosshair'},{value: 2, text: 'Shared Tooltip'}]"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 2">
|
||||
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
|
||||
<gf-time-picker-settings dashboard="ctrl.dashboard"></gf-time-picker-settings>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -55,12 +55,10 @@ export class SaveDashboardAsModalCtrl {
|
||||
// remove alerts if source dashboard is already persisted
|
||||
// do not want to create alert dupes
|
||||
if (dashboard.id > 0) {
|
||||
this.clone.rows.forEach(row => {
|
||||
row.panels.forEach(panel => {
|
||||
this.clone.panels.forEach(panel => {
|
||||
delete panel.thresholds;
|
||||
delete panel.alert;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delete this.clone.autoUpdate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {describe, beforeEach, it, expect} from 'test/lib/common';
|
||||
|
||||
import _ from 'lodash';
|
||||
import {DashboardModel} from '../model';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
|
||||
describe('DashboardModel', function() {
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
|
||||
import _ from 'lodash';
|
||||
import config from 'app/core/config';
|
||||
import {DashboardExporter} from '../export/exporter';
|
||||
import {DashboardModel} from '../model';
|
||||
import {DashboardModel} from '../DashboardModel';
|
||||
|
||||
describe('given dashboard with repeated panels', function() {
|
||||
var dash, exported;
|
||||
|
||||
@@ -9,7 +9,7 @@ function (angular, _, $, config) {
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('dashboardViewStateSrv', function($location, $timeout) {
|
||||
module.factory('dashboardViewStateSrv', function($location, $timeout, $rootScope) {
|
||||
|
||||
// represents the transient view state
|
||||
// like fullscreen panel & edit
|
||||
@@ -154,7 +154,8 @@ function (angular, _, $, config) {
|
||||
|
||||
ctrl.editMode = false;
|
||||
ctrl.fullscreen = false;
|
||||
ctrl.dashboard.editMode = this.oldDashboardEditMode;
|
||||
|
||||
this.dashboard.setViewMode(ctrl.panel, false, false);
|
||||
|
||||
this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id});
|
||||
|
||||
@@ -162,9 +163,9 @@ function (angular, _, $, config) {
|
||||
|
||||
$timeout(function() {
|
||||
if (self.oldTimeRange !== ctrl.range) {
|
||||
self.$scope.broadcastRefresh();
|
||||
$rootScope.$broadcast('refresh');
|
||||
} else {
|
||||
self.$scope.$broadcast('render');
|
||||
$rootScope.$broadcast('render');
|
||||
}
|
||||
delete self.fullscreenPanel;
|
||||
});
|
||||
@@ -176,18 +177,11 @@ function (angular, _, $, config) {
|
||||
ctrl.editMode = this.state.edit && this.dashboard.meta.canEdit;
|
||||
ctrl.fullscreen = true;
|
||||
|
||||
this.oldDashboardEditMode = this.dashboard.editMode;
|
||||
this.oldTimeRange = ctrl.range;
|
||||
this.fullscreenPanel = panelScope;
|
||||
this.dashboard.editMode = false;
|
||||
|
||||
$(window).scrollTop(0);
|
||||
|
||||
this.dashboard.setViewMode(ctrl.panel, true, ctrl.editMode);
|
||||
this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id});
|
||||
|
||||
$timeout(function() {
|
||||
ctrl.render();
|
||||
});
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.registerPanel = function(panelScope) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import {DashboardModel} from '../dashboard/model';
|
||||
import {DashboardModel} from '../dashboard/DashboardModel';
|
||||
import Remarkable from 'remarkable';
|
||||
|
||||
export class MetricsTabCtrl {
|
||||
|
||||
@@ -3,7 +3,7 @@ import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import {profiler} from 'app/core/profiler';
|
||||
import Remarkable from 'remarkable';
|
||||
import {CELL_HEIGHT, CELL_VMARGIN} from '../dashboard/model';
|
||||
import {CELL_HEIGHT, CELL_VMARGIN} from '../dashboard/DashboardModel';
|
||||
|
||||
const TITLE_HEIGHT = 25;
|
||||
const EMPTY_TITLE_HEIGHT = 9;
|
||||
@@ -37,7 +37,7 @@ export class PanelCtrl {
|
||||
this.$scope = $scope;
|
||||
this.$timeout = $injector.get('$timeout');
|
||||
this.editorTabIndex = 0;
|
||||
this.events = new Emitter();
|
||||
this.events = this.panel.events;
|
||||
this.timing = {};
|
||||
|
||||
var plugin = config.panels[this.panel.type];
|
||||
@@ -47,21 +47,14 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
$scope.$on("refresh", () => this.refresh());
|
||||
$scope.$on("render", () => this.render());
|
||||
$scope.$on("$destroy", () => {
|
||||
this.events.emit('panel-teardown');
|
||||
this.events.removeAllListeners();
|
||||
});
|
||||
|
||||
// we should do something interesting
|
||||
// with newly added panels
|
||||
if (this.panel.isNew) {
|
||||
delete this.panel.isNew;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.calculatePanelHeight();
|
||||
this.events.on('panel-size-changed', this.onSizeChanged.bind(this));
|
||||
this.publishAppEvent('panel-initialized', {scope: this.$scope});
|
||||
this.events.emit('panel-initialized');
|
||||
}
|
||||
@@ -170,23 +163,24 @@ export class PanelCtrl {
|
||||
var fullscreenHeight = Math.floor(docHeight * 0.8);
|
||||
this.containerHeight = this.editMode ? editHeight : fullscreenHeight;
|
||||
} else {
|
||||
this.containerHeight = this.panel.height * CELL_HEIGHT + ((this.panel.height-1) * CELL_VMARGIN);
|
||||
this.containerHeight = this.panel.gridPos.h * CELL_HEIGHT + ((this.panel.gridPos.h-1) * CELL_VMARGIN);
|
||||
}
|
||||
|
||||
this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
|
||||
}
|
||||
|
||||
render(payload?) {
|
||||
// ignore if other panel is in fullscreen mode
|
||||
if (this.otherPanelInFullscreenMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.calculatePanelHeight();
|
||||
this.timing.renderStart = new Date().getTime();
|
||||
this.events.emit('render', payload);
|
||||
}
|
||||
|
||||
private onSizeChanged() {
|
||||
this.calculatePanelHeight();
|
||||
this.$timeout(() => {
|
||||
this.render();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
duplicate() {
|
||||
this.dashboard.duplicatePanel(this.panel);
|
||||
this.$timeout(() => {
|
||||
|
||||
@@ -98,6 +98,7 @@ module.directive('grafanaPanel', function($rootScope, $document) {
|
||||
}
|
||||
|
||||
ctrl.events.on('render', () => {
|
||||
console.log('panelDirective::render!');
|
||||
if (lastHeight !== ctrl.containerHeight) {
|
||||
panelContainer.css({minHeight: ctrl.containerHeight});
|
||||
lastHeight = ctrl.containerHeight;
|
||||
|
||||
@@ -88,7 +88,7 @@ function panelHeader($compile) {
|
||||
let menuScope;
|
||||
|
||||
elem.click(function(evt) {
|
||||
const targetClass = evt.target.className;
|
||||
//const targetClass = evt.target.className;
|
||||
|
||||
// remove existing scope
|
||||
if (menuScope) {
|
||||
@@ -100,10 +100,10 @@ function panelHeader($compile) {
|
||||
menuElem.html(menuHtml);
|
||||
$compile(menuElem)(menuScope);
|
||||
|
||||
if (targetClass === 'panel-title-text' || targetClass === 'panel-title') {
|
||||
evt.stopPropagation();
|
||||
elem.find('[data-toggle=dropdown]').dropdown('toggle');
|
||||
}
|
||||
// if (targetClass === 'panel-title-text' || targetClass === 'panel-title') {
|
||||
// evt.stopPropagation();
|
||||
// elem.find('[data-toggle=dropdown]').dropdown('toggle');
|
||||
// }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
var componentInfo: any = {
|
||||
name: 'panel-plugin-' + scope.panel.type,
|
||||
bindings: {dashboard: "=", panel: "=", row: "="},
|
||||
attrs: {dashboard: "ctrl.dashboard", panel: "panel", row: "ctrl.row"},
|
||||
attrs: {dashboard: "dashboard", panel: "panel"},
|
||||
};
|
||||
|
||||
let panelInfo = config.panels[scope.panel.type];
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<div dash-class ng-if="dashboard">
|
||||
<dashnav dashboard="dashboard"></dashnav>
|
||||
<div dash-class ng-if="ctrl.dashboard">
|
||||
<dashnav dashboard="ctrl.dashboard"></dashnav>
|
||||
|
||||
<div class="scroll-canvas scroll-canvas--dashboard">
|
||||
<div gemini-scrollbar>
|
||||
<div dash-editor-view class="dash-edit-view"></div>
|
||||
<div class="dashboard-container">
|
||||
|
||||
<dashboard-submenu ng-if="dashboard.meta.submenuEnabled" dashboard="dashboard"></dashboard-submenu>
|
||||
<dashboard-submenu ng-if="ctrl.dashboard.meta.submenuEnabled" dashboard="ctrl.dashboard">
|
||||
</dashboard-submenu>
|
||||
|
||||
<dash-grid dashboard="dashboard"></dash-grid>
|
||||
<dashboard-grid get-panel-container="ctrl.getPanelContainer">
|
||||
</dashboard-grid>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,9 +26,9 @@ describe('grafanaGraph', function() {
|
||||
|
||||
beforeEach(angularMocks.inject(function($rootScope, $compile) {
|
||||
var ctrl: any = {
|
||||
events: new Emitter(),
|
||||
height: 200,
|
||||
panel: {
|
||||
events: new Emitter(),
|
||||
legend: {},
|
||||
grid: { },
|
||||
yaxes: [
|
||||
@@ -65,7 +65,7 @@ describe('grafanaGraph', function() {
|
||||
|
||||
var scope = $rootScope.$new();
|
||||
scope.ctrl = ctrl;
|
||||
|
||||
scope.ctrl.events = ctrl.panel.events;
|
||||
|
||||
$rootScope.onAppEvent = sinon.spy();
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
{
|
||||
"content": "<div class=\"text-center dashboard-header\">\n <span>Home Dashboard</span>\n</div>",
|
||||
"editable": true,
|
||||
"height": 2,
|
||||
"id": 1,
|
||||
"links": [],
|
||||
"mode": "html",
|
||||
@@ -21,14 +20,16 @@
|
||||
"title": "",
|
||||
"transparent": true,
|
||||
"type": "text",
|
||||
"width": 12,
|
||||
"gridPos": {
|
||||
"w": 12,
|
||||
"h": 2,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"folderId": 0,
|
||||
"headings": true,
|
||||
"height": 17,
|
||||
"id": 3,
|
||||
"limit": 4,
|
||||
"links": [],
|
||||
@@ -40,26 +41,31 @@
|
||||
"title": "",
|
||||
"transparent": false,
|
||||
"type": "dashlist",
|
||||
"width": 7,
|
||||
"gridPos": {
|
||||
"w": 7,
|
||||
"h": 17,
|
||||
"x": 0,
|
||||
"y": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"height": 17,
|
||||
"id": 4,
|
||||
"links": [],
|
||||
"title": "",
|
||||
"transparent": false,
|
||||
"type": "pluginlist",
|
||||
"width": 5,
|
||||
"gridPos": {
|
||||
"w": 5,
|
||||
"h": 17,
|
||||
"x": 7,
|
||||
"y": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"rows": [],
|
||||
"schemaVersion": 15,
|
||||
"schemaVersion": 16,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
|
||||
@@ -77,11 +77,12 @@
|
||||
@import "components/tabbed_view";
|
||||
@import "components/query_part";
|
||||
@import "components/jsontree";
|
||||
@import "components/edit_sidemenu.scss";
|
||||
@import "components/edit_sidemenu";
|
||||
@import "components/row.scss";
|
||||
@import "components/gridstack.scss";
|
||||
@import "components/json_explorer.scss";
|
||||
@import "components/code_editor.scss";
|
||||
@import "components/json_explorer";
|
||||
@import "components/code_editor";
|
||||
@import "components/dashboard_grid";
|
||||
|
||||
|
||||
// PAGES
|
||||
@import "pages/login";
|
||||
|
||||
@@ -105,7 +105,7 @@ $tight-form-bg: $dark-3;
|
||||
$tight-form-func-bg: #333;
|
||||
$tight-form-func-highlight-bg: #444;
|
||||
|
||||
$modal-background: $black;
|
||||
$modal-backdrop-bg: $dark-3;
|
||||
$code-tag-bg: $gray-1;
|
||||
$code-tag-border: lighten($code-tag-bg, 2%);
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ $tight-form-bg: $gray-6;
|
||||
$tight-form-func-bg: $gray-5;
|
||||
$tight-form-func-highlight-bg: $gray-6;
|
||||
|
||||
$modal-background: $body-bg;
|
||||
$modal-backdrop-bg: $body-bg;
|
||||
$code-tag-bg: $gray-6;
|
||||
$code-tag-border: darken($code-tag-bg, 3%);
|
||||
|
||||
|
||||
@@ -222,8 +222,8 @@ $btn-border-radius: 3px;
|
||||
$side-menu-width: 60px;
|
||||
|
||||
// dashboard
|
||||
$panel-margin: 0.4rem;
|
||||
$dashboard-padding: ($panel-margin * 2) $panel-margin $panel-margin $panel-margin;
|
||||
$panel-margin: 10px;
|
||||
$dashboard-padding: $panel-margin * 2;
|
||||
|
||||
// tabs
|
||||
$tabs-padding-top: 0.6rem;
|
||||
|
||||
24
public/sass/components/_dashboard_grid.scss
Normal file
24
public/sass/components/_dashboard_grid.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
@import "~react-grid-layout/css/styles.css";
|
||||
@import "~react-resizable/css/styles.css";
|
||||
|
||||
.panel-in-fullscreen {
|
||||
|
||||
.react-grid-layout {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.react-grid-item {
|
||||
display: none;
|
||||
transition-property: none !important;
|
||||
}
|
||||
|
||||
.panel--fullscreen {
|
||||
display: block !important;
|
||||
position: unset !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
transform: translate(0px, 0px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
.grid-stack-item > .ui-resizable-handle {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.grid-stack {
|
||||
position: relative;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-rtl {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-rtl > .grid-stack-item {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.grid-stack .grid-stack-placeholder > .placeholder-content {
|
||||
background: $input-label-bg;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 5px rgba(82,168,236,10.8);
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item {
|
||||
min-width: 8.3333333333%;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item > .grid-stack-item-content {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 7px;
|
||||
right: 7px;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item > .ui-resizable-handle {
|
||||
position: absolute;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
font-size: 10px;
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item.ui-resizable-disabled > .ui-resizable-handle,
|
||||
.grid-stack > .grid-stack-item.ui-resizable-autohide > .ui-resizable-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item.ui-draggable-dragging, .grid-stack > .grid-stack-item.ui-resizable-resizing {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item.ui-draggable-dragging > .grid-stack-item-content,
|
||||
.grid-stack > .grid-stack-item.ui-draggable-dragging > .grid-stack-item-content, .grid-stack > .grid-stack-item.ui-resizable-resizing > .grid-stack-item-content,
|
||||
.grid-stack > .grid-stack-item.ui-resizable-resizing > .grid-stack-item-content {
|
||||
box-shadow: 1px 4px 6px rgba(0, 0, 0, 0.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item > .ui-resizable-se,
|
||||
.grid-stack > .grid-stack-item > .ui-resizable-sw {
|
||||
font-family: 'grafana-icons' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
&::before {
|
||||
content: "\e90b";
|
||||
}
|
||||
}
|
||||
.grid-stack > .grid-stack-item > .ui-resizable-se {
|
||||
cursor: se-resize;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
right: 6px;
|
||||
bottom: -2px;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item.ui-draggable-dragging > .ui-resizable-handle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='1'] {
|
||||
width: 8.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='1'] {
|
||||
left: 8.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='1'] {
|
||||
min-width: 8.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='1'] {
|
||||
max-width: 8.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='2'] {
|
||||
width: 16.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='2'] {
|
||||
left: 16.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='2'] {
|
||||
min-width: 16.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='2'] {
|
||||
max-width: 16.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='3'] {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='3'] {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='3'] {
|
||||
min-width: 25%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='3'] {
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='4'] {
|
||||
width: 33.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='4'] {
|
||||
left: 33.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='4'] {
|
||||
min-width: 33.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='4'] {
|
||||
max-width: 33.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='5'] {
|
||||
width: 41.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='5'] {
|
||||
left: 41.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='5'] {
|
||||
min-width: 41.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='5'] {
|
||||
max-width: 41.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='6'] {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='6'] {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='6'] {
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='6'] {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='7'] {
|
||||
width: 58.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='7'] {
|
||||
left: 58.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='7'] {
|
||||
min-width: 58.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='7'] {
|
||||
max-width: 58.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='8'] {
|
||||
width: 66.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='8'] {
|
||||
left: 66.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='8'] {
|
||||
min-width: 66.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='8'] {
|
||||
max-width: 66.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='9'] {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='9'] {
|
||||
left: 75%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='9'] {
|
||||
min-width: 75%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='9'] {
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='10'] {
|
||||
width: 83.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='10'] {
|
||||
left: 83.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='10'] {
|
||||
min-width: 83.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='10'] {
|
||||
max-width: 83.3333333333%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='11'] {
|
||||
width: 91.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='11'] {
|
||||
left: 91.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='11'] {
|
||||
min-width: 91.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='11'] {
|
||||
max-width: 91.6666666667%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-width='12'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-x='12'] {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-min-width='12'] {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.grid-stack > .grid-stack-item[data-gs-max-width='12'] {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-animate,
|
||||
.grid-stack.grid-stack-animate .grid-stack-item {
|
||||
-webkit-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
|
||||
-moz-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
|
||||
-ms-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
|
||||
-o-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
|
||||
transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-animate .grid-stack-item.ui-draggable-dragging,
|
||||
.grid-stack.grid-stack-animate .grid-stack-item.ui-resizable-resizing,
|
||||
.grid-stack.grid-stack-animate .grid-stack-item.grid-stack-placeholder {
|
||||
-webkit-transition: left 0s, top 0s, height 0s, width 0s;
|
||||
-moz-transition: left 0s, top 0s, height 0s, width 0s;
|
||||
-ms-transition: left 0s, top 0s, height 0s, width 0s;
|
||||
-o-transition: left 0s, top 0s, height 0s, width 0s;
|
||||
transition: left 0s, top 0s, height 0s, width 0s;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-one-column-mode {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-one-column-mode > .grid-stack-item {
|
||||
position: relative !important;
|
||||
width: auto !important;
|
||||
left: 0 !important;
|
||||
top: auto !important;
|
||||
margin-bottom: 20px;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.grid-stack.grid-stack-one-column-mode > .grid-stack-item > .ui-resizable-handle {
|
||||
display: none;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $zindex-modal-backdrop;
|
||||
background-color: $black;
|
||||
background-color: $modal-backdrop-bg;
|
||||
}
|
||||
|
||||
.modal-backdrop,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
.sidemenu-open {
|
||||
.navbar {
|
||||
padding-left: 0;
|
||||
padding-left: $panel-margin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
margin: 0 $panel-margin ($panel-margin*2) $panel-margin;
|
||||
margin: 0 0 $panel-margin 0;
|
||||
}
|
||||
|
||||
.annotation-disabled, .annotation-disabled a {
|
||||
|
||||
@@ -25,6 +25,7 @@ div.flot-text {
|
||||
background-color: $panel-bg;
|
||||
border: $panel-border;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
&.panel-transparent {
|
||||
background-color: transparent;
|
||||
@@ -34,6 +35,7 @@ div.flot-text {
|
||||
|
||||
.panel-content {
|
||||
padding: 0px 10px 5px 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-title-container {
|
||||
|
||||
4
public/test/specs/helpers.d.ts
vendored
4
public/test/specs/helpers.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare let helpers: any;
|
||||
export default helpers;
|
||||
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
define([
|
||||
'lodash',
|
||||
'app/core/config',
|
||||
'app/core/utils/datemath',
|
||||
], function(_, config, dateMath) {
|
||||
'use strict';
|
||||
|
||||
function ControllerTestContext() {
|
||||
var self = this;
|
||||
|
||||
this.datasource = {};
|
||||
this.$element = {};
|
||||
this.annotationsSrv = {};
|
||||
this.timeSrv = new TimeSrvStub();
|
||||
this.templateSrv = new TemplateSrvStub();
|
||||
this.datasourceSrv = {
|
||||
getMetricSources: function() {},
|
||||
get: function() {
|
||||
return {
|
||||
then: function(callback) {
|
||||
callback(self.datasource);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
this.providePhase = function(mocks) {
|
||||
return window.module(function($provide) {
|
||||
$provide.value('datasourceSrv', self.datasourceSrv);
|
||||
$provide.value('annotationsSrv', self.annotationsSrv);
|
||||
$provide.value('timeSrv', self.timeSrv);
|
||||
$provide.value('templateSrv', self.templateSrv);
|
||||
$provide.value('$element', self.$element);
|
||||
_.each(mocks, function(value, key) {
|
||||
$provide.value(key, value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createPanelController = function(Ctrl) {
|
||||
return window.inject(function($controller, $rootScope, $q, $location, $browser) {
|
||||
self.scope = $rootScope.$new();
|
||||
self.$location = $location;
|
||||
self.$browser = $browser;
|
||||
self.$q = $q;
|
||||
self.panel = {type: 'test'};
|
||||
self.dashboard = {meta: {}};
|
||||
|
||||
$rootScope.appEvent = sinon.spy();
|
||||
$rootScope.onAppEvent = sinon.spy();
|
||||
$rootScope.colors = [];
|
||||
|
||||
for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); }
|
||||
|
||||
config.panels['test'] = {info: {}};
|
||||
self.ctrl = $controller(Ctrl, {$scope: self.scope}, {
|
||||
panel: self.panel, dashboard: self.dashboard, row: {}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createControllerPhase = function(controllerName) {
|
||||
return window.inject(function($controller, $rootScope, $q, $location, $browser) {
|
||||
self.scope = $rootScope.$new();
|
||||
self.$location = $location;
|
||||
self.$browser = $browser;
|
||||
self.scope.contextSrv = {};
|
||||
self.scope.panel = {};
|
||||
self.scope.row = { panels:[] };
|
||||
self.scope.dashboard = {meta: {}};
|
||||
self.scope.dashboardMeta = {};
|
||||
self.scope.dashboardViewState = new DashboardViewStateStub();
|
||||
self.scope.appEvent = sinon.spy();
|
||||
self.scope.onAppEvent = sinon.spy();
|
||||
|
||||
$rootScope.colors = [];
|
||||
for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); }
|
||||
|
||||
self.$q = $q;
|
||||
self.scope.skipDataOnInit = true;
|
||||
self.scope.skipAutoInit = true;
|
||||
self.controller = $controller(controllerName, {
|
||||
$scope: self.scope
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function ServiceTestContext() {
|
||||
var self = this;
|
||||
self.templateSrv = new TemplateSrvStub();
|
||||
self.timeSrv = new TimeSrvStub();
|
||||
self.datasourceSrv = {};
|
||||
self.backendSrv = {};
|
||||
self.$routeParams = {};
|
||||
|
||||
this.providePhase = function(mocks) {
|
||||
return window.module(function($provide) {
|
||||
_.each(mocks, function(key) {
|
||||
$provide.value(key, self[key]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createService = function(name) {
|
||||
return window.inject(function($q, $rootScope, $httpBackend, $injector, $location, $timeout) {
|
||||
self.$q = $q;
|
||||
self.$rootScope = $rootScope;
|
||||
self.$httpBackend = $httpBackend;
|
||||
self.$location = $location;
|
||||
|
||||
self.$rootScope.onAppEvent = function() {};
|
||||
self.$rootScope.appEvent = function() {};
|
||||
self.$timeout = $timeout;
|
||||
|
||||
self.service = $injector.get(name);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function DashboardViewStateStub() {
|
||||
this.registerPanel = function() {
|
||||
};
|
||||
}
|
||||
|
||||
function TimeSrvStub() {
|
||||
this.init = sinon.spy();
|
||||
this.time = { from:'now-1h', to: 'now'};
|
||||
this.timeRange = function(parse) {
|
||||
if (parse === false) {
|
||||
return this.time;
|
||||
}
|
||||
return {
|
||||
from : dateMath.parse(this.time.from, false),
|
||||
to : dateMath.parse(this.time.to, true)
|
||||
};
|
||||
};
|
||||
|
||||
this.replace = function(target) {
|
||||
return target;
|
||||
};
|
||||
|
||||
this.setTime = function(time) {
|
||||
this.time = time;
|
||||
};
|
||||
}
|
||||
|
||||
function ContextSrvStub() {
|
||||
this.hasRole = function() {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
function TemplateSrvStub() {
|
||||
this.variables = [];
|
||||
this.templateSettings = { interpolate : /\[\[([\s\S]+?)\]\]/g };
|
||||
this.data = {};
|
||||
this.replace = function(text) {
|
||||
return _.template(text, this.templateSettings)(this.data);
|
||||
};
|
||||
this.init = function() {};
|
||||
this.getAdhocFilters = function() { return []; };
|
||||
this.fillVariableValuesForUrl = function() {};
|
||||
this.updateTemplateData = function() { };
|
||||
this.variableExists = function() { return false; };
|
||||
this.variableInitialized = function() { };
|
||||
this.highlightVariablesAsHtml = function(str) { return str; };
|
||||
this.setGrafanaVariable = function(name, value) {
|
||||
this.data[name] = value;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ControllerTestContext: ControllerTestContext,
|
||||
TimeSrvStub: TimeSrvStub,
|
||||
ContextSrvStub: ContextSrvStub,
|
||||
ServiceTestContext: ServiceTestContext
|
||||
};
|
||||
|
||||
});
|
||||
195
public/test/specs/helpers.ts
Normal file
195
public/test/specs/helpers.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import _ from 'lodash';
|
||||
import config from 'app/core/config';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import {angularMocks, sinon} from '../lib/common';
|
||||
import {PanelModel} from 'app/features/dashboard/PanelModel';
|
||||
|
||||
export function ControllerTestContext() {
|
||||
var self = this;
|
||||
|
||||
this.datasource = {};
|
||||
this.$element = {};
|
||||
this.annotationsSrv = {};
|
||||
this.timeSrv = new TimeSrvStub();
|
||||
this.templateSrv = new TemplateSrvStub();
|
||||
this.datasourceSrv = {
|
||||
getMetricSources: function() {},
|
||||
get: function() {
|
||||
return {
|
||||
then: function(callback) {
|
||||
callback(self.datasource);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
this.providePhase = function(mocks) {
|
||||
return angularMocks.module(function($provide) {
|
||||
$provide.value('datasourceSrv', self.datasourceSrv);
|
||||
$provide.value('annotationsSrv', self.annotationsSrv);
|
||||
$provide.value('timeSrv', self.timeSrv);
|
||||
$provide.value('templateSrv', self.templateSrv);
|
||||
$provide.value('$element', self.$element);
|
||||
_.each(mocks, function(value, key) {
|
||||
$provide.value(key, value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createPanelController = function(Ctrl) {
|
||||
return angularMocks.inject(function($controller, $rootScope, $q, $location, $browser) {
|
||||
self.scope = $rootScope.$new();
|
||||
self.$location = $location;
|
||||
self.$browser = $browser;
|
||||
self.$q = $q;
|
||||
self.panel = new PanelModel({type: 'test'});
|
||||
self.dashboard = {meta: {}};
|
||||
|
||||
$rootScope.appEvent = sinon.spy();
|
||||
$rootScope.onAppEvent = sinon.spy();
|
||||
$rootScope.colors = [];
|
||||
|
||||
for (var i = 0; i < 50; i++) {
|
||||
$rootScope.colors.push('#' + i);
|
||||
}
|
||||
|
||||
config.panels['test'] = {info: {}};
|
||||
self.ctrl = $controller(
|
||||
Ctrl,
|
||||
{$scope: self.scope},
|
||||
{
|
||||
panel: self.panel,
|
||||
dashboard: self.dashboard,
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
this.createControllerPhase = function(controllerName) {
|
||||
return angularMocks.inject(function($controller, $rootScope, $q, $location, $browser) {
|
||||
self.scope = $rootScope.$new();
|
||||
self.$location = $location;
|
||||
self.$browser = $browser;
|
||||
self.scope.contextSrv = {};
|
||||
self.scope.panel = {};
|
||||
self.scope.dashboard = {meta: {}};
|
||||
self.scope.dashboardMeta = {};
|
||||
self.scope.dashboardViewState = new DashboardViewStateStub();
|
||||
self.scope.appEvent = sinon.spy();
|
||||
self.scope.onAppEvent = sinon.spy();
|
||||
|
||||
$rootScope.colors = [];
|
||||
for (var i = 0; i < 50; i++) {
|
||||
$rootScope.colors.push('#' + i);
|
||||
}
|
||||
|
||||
self.$q = $q;
|
||||
self.scope.skipDataOnInit = true;
|
||||
self.scope.skipAutoInit = true;
|
||||
self.controller = $controller(controllerName, {
|
||||
$scope: self.scope,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function ServiceTestContext() {
|
||||
var self = this;
|
||||
self.templateSrv = new TemplateSrvStub();
|
||||
self.timeSrv = new TimeSrvStub();
|
||||
self.datasourceSrv = {};
|
||||
self.backendSrv = {};
|
||||
self.$routeParams = {};
|
||||
|
||||
this.providePhase = function(mocks) {
|
||||
return angularMocks.module(function($provide) {
|
||||
_.each(mocks, function(key) {
|
||||
$provide.value(key, self[key]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createService = function(name) {
|
||||
return angularMocks.inject(function($q, $rootScope, $httpBackend, $injector, $location, $timeout) {
|
||||
self.$q = $q;
|
||||
self.$rootScope = $rootScope;
|
||||
self.$httpBackend = $httpBackend;
|
||||
self.$location = $location;
|
||||
|
||||
self.$rootScope.onAppEvent = function() {};
|
||||
self.$rootScope.appEvent = function() {};
|
||||
self.$timeout = $timeout;
|
||||
|
||||
self.service = $injector.get(name);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function DashboardViewStateStub() {
|
||||
this.registerPanel = function() {};
|
||||
}
|
||||
|
||||
export function TimeSrvStub() {
|
||||
this.init = sinon.spy();
|
||||
this.time = {from: 'now-1h', to: 'now'};
|
||||
this.timeRange = function(parse) {
|
||||
if (parse === false) {
|
||||
return this.time;
|
||||
}
|
||||
return {
|
||||
from: dateMath.parse(this.time.from, false),
|
||||
to: dateMath.parse(this.time.to, true),
|
||||
};
|
||||
};
|
||||
|
||||
this.replace = function(target) {
|
||||
return target;
|
||||
};
|
||||
|
||||
this.setTime = function(time) {
|
||||
this.time = time;
|
||||
};
|
||||
}
|
||||
|
||||
export function ContextSrvStub() {
|
||||
this.hasRole = function() {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function TemplateSrvStub() {
|
||||
this.variables = [];
|
||||
this.templateSettings = {interpolate: /\[\[([\s\S]+?)\]\]/g};
|
||||
this.data = {};
|
||||
this.replace = function(text) {
|
||||
return _.template(text, this.templateSettings)(this.data);
|
||||
};
|
||||
this.init = function() {};
|
||||
this.getAdhocFilters = function() {
|
||||
return [];
|
||||
};
|
||||
this.fillVariableValuesForUrl = function() {};
|
||||
this.updateTemplateData = function() {};
|
||||
this.variableExists = function() {
|
||||
return false;
|
||||
};
|
||||
this.variableInitialized = function() {};
|
||||
this.highlightVariablesAsHtml = function(str) {
|
||||
return str;
|
||||
};
|
||||
this.setGrafanaVariable = function(name, value) {
|
||||
this.data[name] = value;
|
||||
};
|
||||
}
|
||||
|
||||
var allDeps = {
|
||||
ContextSrvStub: ContextSrvStub,
|
||||
TemplateSrvStub: TemplateSrvStub,
|
||||
TimeSrvStub: TimeSrvStub,
|
||||
ControllerTestContext: ControllerTestContext,
|
||||
ServiceTestContext: ServiceTestContext,
|
||||
DashboardViewStateStub: DashboardViewStateStub
|
||||
};
|
||||
|
||||
// for legacy
|
||||
export default allDeps;
|
||||
@@ -1,130 +0,0 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// Tun on full stack traces in errors to help debugging
|
||||
Error.stackTraceLimit=Infinity;
|
||||
|
||||
window.__karma__.loaded = function() {};
|
||||
|
||||
System.config({
|
||||
baseURL: '/base/',
|
||||
defaultJSExtensions: true,
|
||||
paths: {
|
||||
'mousetrap': 'vendor/npm/mousetrap/mousetrap.js',
|
||||
'eventemitter3': 'vendor/npm/eventemitter3/index.js',
|
||||
'remarkable': 'vendor/npm/remarkable/dist/remarkable.js',
|
||||
'tether': 'vendor/npm/tether/dist/js/tether.js',
|
||||
'tether-drop': 'vendor/npm/tether-drop/dist/js/drop.js',
|
||||
'moment': 'vendor/moment.js',
|
||||
"jquery": "vendor/jquery/dist/jquery.js",
|
||||
'lodash-src': 'vendor/lodash/dist/lodash.js',
|
||||
"lodash": 'app/core/lodash_extended.js',
|
||||
"angular": 'vendor/angular/angular.js',
|
||||
'angular-mocks': 'vendor/angular-mocks/angular-mocks.js',
|
||||
"bootstrap": "vendor/bootstrap/bootstrap.js",
|
||||
'angular-route': 'vendor/angular-route/angular-route.js',
|
||||
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize.js',
|
||||
"angular-ui": "vendor/angular-ui/ui-bootstrap-tpls.js",
|
||||
"angular-strap": "vendor/angular-other/angular-strap.js",
|
||||
"angular-dragdrop": "vendor/angular-native-dragdrop/draganddrop.js",
|
||||
"angular-bindonce": "vendor/angular-bindonce/bindonce.js",
|
||||
"spectrum": "vendor/spectrum.js",
|
||||
"bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
|
||||
"jquery.flot": "vendor/flot/jquery.flot",
|
||||
"jquery.flot.pie": "vendor/flot/jquery.flot.pie",
|
||||
"jquery.flot.selection": "vendor/flot/jquery.flot.selection",
|
||||
"jquery.flot.stack": "vendor/flot/jquery.flot.stack",
|
||||
"jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",
|
||||
"jquery.flot.time": "vendor/flot/jquery.flot.time",
|
||||
"jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair",
|
||||
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
|
||||
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
|
||||
"d3": "vendor/d3/d3.js",
|
||||
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
|
||||
"twemoji": "vendor/npm/twemoji/2/twemoji.amd.js",
|
||||
"ace": "vendor/npm/ace-builds/src-noconflict/ace",
|
||||
},
|
||||
|
||||
packages: {
|
||||
app: {
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
vendor: {
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
},
|
||||
|
||||
map: {
|
||||
},
|
||||
|
||||
meta: {
|
||||
'vendor/angular/angular.js': {
|
||||
format: 'global',
|
||||
deps: ['jquery'],
|
||||
exports: 'angular',
|
||||
},
|
||||
'vendor/angular-mocks/angular-mocks.js': {
|
||||
format: 'global',
|
||||
deps: ['angular'],
|
||||
},
|
||||
'vendor/npm/eventemitter3/index.js': {
|
||||
format: 'cjs',
|
||||
exports: 'EventEmitter'
|
||||
},
|
||||
'vendor/npm/mousetrap/mousetrap.js': {
|
||||
format: 'global',
|
||||
exports: 'Mousetrap'
|
||||
},
|
||||
'vendor/npm/ace-builds/src-noconflict/ace.js': {
|
||||
format: 'global',
|
||||
exports: 'ace'
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
function file2moduleName(filePath) {
|
||||
return filePath.replace(/\\/g, '/')
|
||||
.replace(/^\/base\//, '')
|
||||
.replace(/\.\w*$/, '');
|
||||
}
|
||||
|
||||
function onlySpecFiles(path) {
|
||||
return /specs.*/.test(path);
|
||||
}
|
||||
|
||||
window.grafanaBootData = {settings: {}};
|
||||
|
||||
var modules = ['angular', 'angular-mocks', 'app/app'];
|
||||
var promises = modules.map(function(name) {
|
||||
return System.import(name);
|
||||
});
|
||||
|
||||
Promise.all(promises).then(function(deps) {
|
||||
var angular = deps[0];
|
||||
|
||||
angular.module('grafana', ['ngRoute']);
|
||||
angular.module('grafana.services', ['ngRoute', '$strap.directives']);
|
||||
angular.module('grafana.panels', []);
|
||||
angular.module('grafana.controllers', []);
|
||||
angular.module('grafana.directives', []);
|
||||
angular.module('grafana.filters', []);
|
||||
angular.module('grafana.routes', ['ngRoute']);
|
||||
|
||||
// load specs
|
||||
return Promise.all(
|
||||
Object.keys(window.__karma__.files) // All files served by Karma.
|
||||
.filter(onlySpecFiles)
|
||||
.map(file2moduleName)
|
||||
.map(function(path) {
|
||||
// console.log(path);
|
||||
return System.import(path);
|
||||
}));
|
||||
}).then(function() {
|
||||
window.__karma__.start();
|
||||
}, function(error) {
|
||||
window.__karma__.error(error.stack || error);
|
||||
}).catch(function(error) {
|
||||
window.__karma__.error(error.stack || error);
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -10,7 +10,7 @@ const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: "eval-source-map",
|
||||
devtool: "cheap-module-source-map",
|
||||
|
||||
entry: {
|
||||
dark: './public/sass/grafana.dark.scss',
|
||||
|
||||
68
yarn.lock
68
yarn.lock
@@ -1072,6 +1072,10 @@ base@^0.11.1:
|
||||
mixin-deep "^1.2.0"
|
||||
pascalcase "^0.1.1"
|
||||
|
||||
batch-processor@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||
@@ -1509,6 +1513,10 @@ class-utils@^0.3.5:
|
||||
lazy-cache "^2.0.2"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@2.x, classnames@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
||||
|
||||
clean-css@3.4.x, clean-css@~3.4.2:
|
||||
version "3.4.28"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff"
|
||||
@@ -2411,6 +2419,12 @@ elegant-spinner@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
||||
|
||||
element-resize-detector@^1.1.12:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.12.tgz#8b3fd6eedda17f9c00b360a0ea2df9927ae80ba2"
|
||||
dependencies:
|
||||
batch-processor "^1.0.0"
|
||||
|
||||
elliptic@^6.0.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
|
||||
@@ -3445,14 +3459,6 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6,
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
"gridstack@https://github.com/grafana/gridstack.js#grafana":
|
||||
version "1.0.0-dev"
|
||||
resolved "https://github.com/grafana/gridstack.js#bd40b3fe4dafc99350145c7b4761d8693593f6fe"
|
||||
dependencies:
|
||||
jquery "^3.1.0"
|
||||
jquery-ui "^1.12.0"
|
||||
lodash "^4.14.2"
|
||||
|
||||
growl@1.10.3:
|
||||
version "1.10.3"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
|
||||
@@ -4434,11 +4440,7 @@ jest-validate@^21.1.0:
|
||||
leven "^2.1.0"
|
||||
pretty-format "^21.2.1"
|
||||
|
||||
jquery-ui@^1.12.0:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51"
|
||||
|
||||
jquery@^3.1.0, jquery@^3.2.1:
|
||||
jquery@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
|
||||
|
||||
@@ -4975,6 +4977,10 @@ lodash.flattendeep@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
|
||||
lodash.isequal@^4.0.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
|
||||
lodash.kebabcase@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
||||
@@ -5011,7 +5017,7 @@ lodash@^3.10.1, lodash@^3.5.0, lodash@^3.6.0, lodash@^3.7.0, lodash@^3.8.0, loda
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
||||
|
||||
lodash@^4.0.0, lodash@^4.0.1, lodash@^4.14.0, lodash@^4.14.2, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.4:
|
||||
lodash@^4.0.0, lodash@^4.0.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0, lodash@^4.8.0, lodash@~4.17.4:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
@@ -6719,7 +6725,7 @@ promzard@^0.3.0:
|
||||
dependencies:
|
||||
read "1"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.6.0:
|
||||
prop-types@15.x, prop-types@^15.5.10, prop-types@^15.6.0:
|
||||
version "15.6.0"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
|
||||
dependencies:
|
||||
@@ -6877,6 +6883,38 @@ react-dom@^16.0.0:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
"react-draggable@^2.2.6 || ^3.0.3", react-draggable@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.3.tgz#a6f9b3a7171981b76dadecf238316925cb9eacf4"
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-grid-layout@^0.16.0:
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.16.0.tgz#f74363cd134b2f8a763224d7b6287cbb68e6de05"
|
||||
dependencies:
|
||||
classnames "2.x"
|
||||
lodash.isequal "^4.0.0"
|
||||
prop-types "15.x"
|
||||
react-draggable "^3.0.3"
|
||||
react-resizable "^1.7.5"
|
||||
|
||||
react-resizable@^1.7.5:
|
||||
version "1.7.5"
|
||||
resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e"
|
||||
dependencies:
|
||||
prop-types "15.x"
|
||||
react-draggable "^2.2.6 || ^3.0.3"
|
||||
|
||||
react-sizeme@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.3.6.tgz#d60ea2634acc3fd827a3c7738d41eea0992fa678"
|
||||
dependencies:
|
||||
element-resize-detector "^1.1.12"
|
||||
invariant "^2.2.2"
|
||||
lodash "^4.17.4"
|
||||
|
||||
react-test-renderer@^16.0.0:
|
||||
version "16.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15"
|
||||
|
||||
Reference in New Issue
Block a user