diff --git a/package.json b/package.json index a13b50bea7c..b34ceb1bce3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 8c815401245..7b4a2269426 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -278,12 +278,14 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) { panels := dash.Get("panels").MustArray() newpanel := simplejson.NewFromAny(map[string]interface{}{ - "type": "gettingstarted", - "id": 123123, - "x": 0, - "y": 3, - "width": 12, - "height": 4, + "type": "gettingstarted", + "id": 123123, + "gridPos": map[string]interface{}{ + "x": 0, + "y": 3, + "w": 12, + "h": 4, + }, }) panels = append(panels, newpanel) diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 1ad39aa3924..fcc49799def 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -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 ( - SELECT - dashboard.id FROM dashboard - LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id - `) + FROM `) // add tags filter if len(query.Tags) > 0 { - sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `)`) + sql.WriteString( + `( + SELECT + dashboard.id FROM dashboard + LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id + `) + if query.IsStarred { + sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.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) } - // 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(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 } - sql.WriteString(` WHERE dashboard.org_id=?`) + 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 { @@ -253,16 +285,17 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear if query.SignedInUser.OrgRole != m.ROLE_ADMIN { allowedDashboardsSubQuery := ` AND (dashboard.has_acl = 0 OR dashboard.id in ( - SELECT distinct d.id AS DashboardId + SELECT distinct d.id AS DashboardId FROM dashboard AS d - LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id - LEFT JOIN user_group_member as ugm on ugm.user_group_id = da.user_group_id - LEFT JOIN org_user ou on ou.role = da.role + LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id + LEFT JOIN user_group_member as ugm on ugm.user_group_id = da.user_group_id + LEFT JOIN org_user ou on ou.role = da.role WHERE 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 { diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 84ca3f37686..e0473b0e38f 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -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") diff --git a/pkg/services/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go index 8cb1cf9af9e..28957cb2096 100644 --- a/pkg/services/sqlstore/datasource_test.go +++ b/pkg/services/sqlstore/datasource_test.go @@ -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 { diff --git a/public/app/core/directives/dash_class.js b/public/app/core/directives/dash_class.js index 7ac13a552ec..3dd2bba91af 100644 --- a/public/app/core/directives/dash_class.js +++ b/public/app/core/directives/dash_class.js @@ -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)); }); } diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js index a3bac58945c..02c7d55b99e 100644 --- a/public/app/core/directives/dash_edit_link.js +++ b/public/app/core/directives/dash_edit_link.js @@ -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) { diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 73269403312..1e351054b6d 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -235,42 +235,54 @@ export class BackendSrv { createDashboardFolder(name) { const dash = { + schemaVersion: 16, title: name, editable: true, hideControls: true, - rows: [ + panels: [ { - panels: [ - { - folderId: 0, - headings: false, - limit: 1000, - links: [], - query: '', - recent: false, - search: true, - span: 4, - starred: false, - tags: [], - title: 'Dashboards in this folder', - type: 'dashlist' - }, - { - onlyAlertsOnDashboard: true, - span: 4, - title: 'Alerts in this folder', - type: 'alertlist' - }, - { - span: 4, - title: 'Permissions for this folder', - type: 'permissionlist', - folderId: 0 - } - ], - showTitle: true, - title: name, - titleSize: 'h1' + id: 1, + folderId: 0, + headings: false, + limit: 1000, + links: [], + query: '', + recent: false, + search: true, + starred: false, + tags: [], + title: 'Dashboards in this folder', + type: 'dashlist', + gridPos: { + x: 0, + y: 0, + w: 4, + h: 10 + } + }, + { + id: 2, + onlyAlertsOnDashboard: true, + title: 'Alerts in this folder', + type: 'alertlist', + gridPos: { + x: 4, + y: 0, + w: 4, + h: 10 + } + }, + { + id: 3, + title: 'Permissions for this folder', + type: 'permissionlist', + folderId: 0, + gridPos: { + x: 8, + y: 0, + w: 4, + h: 10 + } } ] }; @@ -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}); }); } diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/DashboardModel.ts similarity index 90% rename from public/app/features/dashboard/model.ts rename to public/app/features/dashboard/DashboardModel.ts index e407a78faa4..ee057722869 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/DashboardModel.ts @@ -1,5 +1,3 @@ -/// - 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 (k = 0; k < panelUpgrades.length; k++) { - panelUpgrades[k].call(this, row.panels[j]); - } + for (j = 0; j < this.panels.length; j++) { + for (k = 0; k < panelUpgrades.length; k++) { + 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; diff --git a/public/app/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts new file mode 100644 index 00000000000..de086170b20 --- /dev/null +++ b/public/app/features/dashboard/PanelModel.ts @@ -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'); + } + } +} + diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 388989bd442..75009abc4ca 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -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' diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 4e6287688f8..b457f6d33c0 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -1,143 +1,143 @@ -/// - 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; + // funcs called from React component bindings and needs this binding + this.getPanelContainer = this.getPanelContainer.bind(this); + } - $scope.setupDashboard = function(data) { - try { - $scope.setupDashboardInternal(data); - } catch (err) { - $scope.onInitFailed(err, 'Dashboard init failed', true); - } - }; + setupDashboard(data) { + try { + this.setupDashboardInternal(data); + } catch (err) { + this.onInitFailed(err, 'Dashboard init failed', true); + } + } - $scope.setupDashboardInternal = function(data) { - var dashboard = dashboardSrv.create(data.dashboard, data.meta); - dashboardSrv.setCurrent(dashboard); + 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); + // init services + 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) - // template values failes are non fatal - .catch($scope.onInitFailed.bind(this, 'Templating init failed', false)) - // continue - .finally(function() { - dynamicDashboardSrv.init(dashboard); - dynamicDashboardSrv.process(); + // template values service needs to initialize completely before + // the rest of the dashboard can load + this.variableSrv.init(dashboard) + // template values failes are non fatal + .catch(this.onInitFailed.bind(this, 'Templating init failed', false)) + // continue + .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); - }) - .catch($scope.onInitFailed.bind(this, 'Dashboard init failed', true)); - }; + this.dashboard.updateSubmenuVisibility(); + this.setWindowTitleAndTheme(); - $scope.onInitFailed = function(msg, fatal, err) { - console.log(msg, err); + this.$scope.appEvent("dashboard-initialized", dashboard); + }) + .catch(this.onInitFailed.bind(this, 'Dashboard init failed', true)); + } - if (err.data && err.data.message) { - err.message = err.data.message; - } else if (!err.message) { - err = {message: err.toString()}; - } + onInitFailed(msg, fatal, err) { + console.log(msg, err); - $scope.appEvent("alert-error", [msg, err.message]); + if (err.data && err.data.message) { + err.message = err.data.message; + } else if (!err.message) { + err = {message: err.toString()}; + } - // protect against recursive fallbacks - if (fatal && !$scope.loadedFallbackDashboard) { - $scope.loadedFallbackDashboard = true; - $scope.setupDashboard({dashboard: {title: 'Dashboard Init failed'}}); - } - }; + this.$scope.appEvent("alert-error", [msg, err.message]); - $scope.templateVariableUpdated = function() { - dynamicDashboardSrv.process(); - }; + // protect against recursive fallbacks + if (fatal && !this.loadedFallbackDashboard) { + this.loadedFallbackDashboard = true; + this.setupDashboard({dashboard: {title: 'Dashboard Init failed'}}); + } + } - $scope.setWindowTitleAndTheme = function() { - window.document.title = config.window_title_prefix + $scope.dashboard.title; - }; + templateVariableUpdated() { + this.dynamicDashboardSrv.process(); + } - $scope.broadcastRefresh = function() { - $rootScope.$broadcast('refresh'); - }; + setWindowTitleAndTheme() { + window.document.title = config.window_title_prefix + this.dashboard.title; + } - $scope.addRowDefault = function() { - $scope.dashboard.addEmptyRow(); - }; + showJsonEditor(evt, options) { + var editScope = this.$rootScope.$new(); + editScope.object = options.object; + editScope.updateHandler = options.updateHandler; + this.$scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope }); + } - $scope.showJsonEditor = function(evt, options) { - var editScope = $rootScope.$new(); - editScope.object = options.object; - editScope.updateHandler = options.updateHandler; - $scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope }); - }; + getDashboard() { + return this.dashboard; + } - $scope.registerWindowResizeEvent = function() { - angular.element(window).bind('resize', function() { - $timeout.cancel(resizeEventTimeout); - resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200); - }); + getPanelLoader() { + return this.panelLoader; + } - $scope.$on('$destroy', function() { - angular.element(window).unbind('resize'); - $scope.dashboard.destroy(); - }); - }; + timezoneChanged() { + this.$rootScope.$broadcast("refresh"); + } - $scope.timezoneChanged = function() { - $rootScope.$broadcast("refresh"); - }; + onFolderChange(folder) { + this.dashboard.folderId = folder.id; + this.dashboard.meta.folderId = folder.id; + this.dashboard.meta.folderTitle= folder.title; + } - $scope.onFolderChange = function(folder) { - $scope.dashboard.folderId = folder.id; - $scope.dashboard.meta.folderId = folder.id; - $scope.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); } } diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts index 77ef4c19d2c..4502d4cdc6e 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/dashboard_srv.ts @@ -1,7 +1,5 @@ -/// - import coreModule from 'app/core/core_module'; -import {DashboardModel} from './model'; +import {DashboardModel} from './DashboardModel'; export class DashboardSrv { dash: any; diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx new file mode 100644 index 00000000000..7f1958836ff --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -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 ( + + {children} + + ); +} + +const SizedReactLayoutGrid = sizeMe({monitorWidth: true})(GridWrapper); + +export interface DashboardGridProps { + getPanelContainer: () => PanelContainer; +} + +export class DashboardGrid extends React.Component { + 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( +
+ +
, + ); + } + + return panelElements; + } + + render() { + return ( + + {this.renderPanels()} + + ); + } +} + +coreModule.directive('dashboardGrid', function(reactDirective) { + return reactDirective(DashboardGrid, [['getPanelContainer', {watchDepth: 'reference', wrapApply: false}]]); +}); diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx new file mode 100644 index 00000000000..5c7f2178c96 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -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 { + 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 ( +
this.element = element} /> + ); + } +} + diff --git a/public/app/features/dashboard/dashgrid/PanelContainer.ts b/public/app/features/dashboard/dashgrid/PanelContainer.ts new file mode 100644 index 00000000000..e2e524ed8da --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelContainer.ts @@ -0,0 +1,8 @@ +import {DashboardModel}  from '../DashboardModel'; +import {PanelLoader} from './PanelLoader'; + +export interface PanelContainer { + getPanelLoader(): PanelLoader; + getDashboard(): DashboardModel; +} + diff --git a/public/app/features/dashboard/dashgrid/PanelLoader.ts b/public/app/features/dashboard/dashgrid/PanelLoader.ts new file mode 100644 index 00000000000..2e9302537af --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelLoader.ts @@ -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 = ''; + 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); diff --git a/public/app/features/dashboard/dashgrid/dashgrid.ts b/public/app/features/dashboard/dashgrid/dashgrid.ts deleted file mode 100644 index 2fda28d0bb1..00000000000 --- a/public/app/features/dashboard/dashgrid/dashgrid.ts +++ /dev/null @@ -1,211 +0,0 @@ -// /// -// -// 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 = ` -//
-// -// -// -// -//
-// `; -// -// 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); diff --git a/public/app/features/dashboard/dashgrid/ref.txt b/public/app/features/dashboard/dashgrid/ref.txt deleted file mode 100644 index 81b1da9e1df..00000000000 --- a/public/app/features/dashboard/dashgrid/ref.txt +++ /dev/null @@ -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 -/// - -import $ from 'jquery'; -import coreModule from '../../core_module'; - -import 'jquery-ui'; -import 'gridstack'; -import 'gridstack.jquery-ui'; - -const template = ` -
-
- - -
-
-`; - -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 diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index fd1ee180248..e350bc33caa 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -70,7 +70,7 @@ diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 182c722b739..4b2587d8ed3 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -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(); diff --git a/public/app/features/dashboard/graphiteImportCtrl.js b/public/app/features/dashboard/graphiteImportCtrl.js deleted file mode 100644 index 53338883274..00000000000 --- a/public/app/features/dashboard/graphiteImportCtrl.js +++ /dev/null @@ -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)); - } - }); -}); diff --git a/public/app/features/dashboard/history/history.ts b/public/app/features/dashboard/history/history.ts index bddcf6bf8d6..98c737e53c3 100644 --- a/public/app/features/dashboard/history/history.ts +++ b/public/app/features/dashboard/history/history.ts @@ -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 { diff --git a/public/app/features/dashboard/history/history_srv.ts b/public/app/features/dashboard/history/history_srv.ts index 6517c36e1a7..7fd4705c823 100644 --- a/public/app/features/dashboard/history/history_srv.ts +++ b/public/app/features/dashboard/history/history_srv.ts @@ -1,8 +1,6 @@ -/// - import _ from 'lodash'; import coreModule from 'app/core/core_module'; -import {DashboardModel} from '../model'; +import {DashboardModel} from '../DashboardModel'; export interface HistoryListOpts { limit: number; diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index 8c7af5d70cd..6a8f4792f72 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -5,7 +5,7 @@
-
+
Details
- +
- +
- +
-
@@ -51,19 +51,19 @@
- +
@@ -79,7 +79,7 @@
- +
@@ -90,7 +90,7 @@
- +
diff --git a/public/app/features/dashboard/save_as_modal.ts b/public/app/features/dashboard/save_as_modal.ts index 6ed70a12781..689718cb7e8 100644 --- a/public/app/features/dashboard/save_as_modal.ts +++ b/public/app/features/dashboard/save_as_modal.ts @@ -55,11 +55,9 @@ 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 => { - delete panel.thresholds; - delete panel.alert; - }); + this.clone.panels.forEach(panel => { + delete panel.thresholds; + delete panel.alert; }); } diff --git a/public/app/features/dashboard/specs/dashboard_model_specs.ts b/public/app/features/dashboard/specs/dashboard_model_specs.ts index ca5482bbfc5..940456cfba1 100644 --- a/public/app/features/dashboard/specs/dashboard_model_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_model_specs.ts @@ -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() { diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index cc2b1ddaf97..2c63ccb45f5 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -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; diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 2847e52ebfa..6820de3189f 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -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) { diff --git a/public/app/features/panel/metrics_tab.ts b/public/app/features/panel/metrics_tab.ts index 026d6fce650..b32e9fc21c1 100644 --- a/public/app/features/panel/metrics_tab.ts +++ b/public/app/features/panel/metrics_tab.ts @@ -1,6 +1,4 @@ -/// - -import {DashboardModel} from '../dashboard/model'; +import {DashboardModel} from '../dashboard/DashboardModel'; import Remarkable from 'remarkable'; export class MetricsTabCtrl { diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 9806be39273..83d43a280f7 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -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'); } @@ -71,7 +64,7 @@ export class PanelCtrl { } refresh() { - this.events.emit('refresh', null); + this.events.emit('refresh', null); } publishAppEvent(evtName, evt) { @@ -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(() => { diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 8dd2779bdd2..1b193ecc2dc 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -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; diff --git a/public/app/features/panel/panel_header.ts b/public/app/features/panel/panel_header.ts index e0f361d7be5..2f94a8d43bf 100644 --- a/public/app/features/panel/panel_header.ts +++ b/public/app/features/panel/panel_header.ts @@ -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'); + // } }); } }; diff --git a/public/app/features/plugins/plugin_component.ts b/public/app/features/plugins/plugin_component.ts index e8ece7b55c4..d162c0794db 100644 --- a/public/app/features/plugins/plugin_component.ts +++ b/public/app/features/plugins/plugin_component.ts @@ -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]; diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 16218f1916c..58fea114aeb 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -1,14 +1,16 @@ -
- +
+
- + + - + +
diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 1c240bb2967..11560e71c47 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -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(); diff --git a/public/dashboards/home.json b/public/dashboards/home.json index d19ecfd6809..91f5abec5eb 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -13,7 +13,6 @@ { "content": "
\n Home Dashboard\n
", "editable": true, - "height": 2, "id": 1, "links": [], "mode": "html", @@ -21,14 +20,16 @@ "title": "", "transparent": true, "type": "text", - "width": 12, - "x": 0, - "y": 0 + "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, - "x": 0, - "y": 6 + "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, - "x": 7, - "y": 6 + "gridPos": { + "w": 5, + "h": 17, + "x": 7, + "y": 6 + } } ], "rows": [], - "schemaVersion": 15, + "schemaVersion": 16, "style": "dark", "tags": [], "templating": { diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 7ccec0ae56a..a4e99987576 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -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"; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 52761e4bfc6..7eea87b2009 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -105,9 +105,9 @@ $tight-form-bg: $dark-3; $tight-form-func-bg: #333; $tight-form-func-highlight-bg: #444; -$modal-background: $black; -$code-tag-bg: $gray-1; -$code-tag-border: lighten($code-tag-bg, 2%); +$modal-backdrop-bg: $dark-3; +$code-tag-bg: $gray-1; +$code-tag-border: lighten($code-tag-bg, 2%); // Lists diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 1e48d515c4d..1bb58532d67 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -112,9 +112,9 @@ $tight-form-bg: $gray-6; $tight-form-func-bg: $gray-5; $tight-form-func-highlight-bg: $gray-6; -$modal-background: $body-bg; -$code-tag-bg: $gray-6; -$code-tag-border: darken($code-tag-bg, 3%); +$modal-backdrop-bg: $body-bg; +$code-tag-bg: $gray-6; +$code-tag-border: darken($code-tag-bg, 3%); // Lists $grafanaListBackground: $gray-6; diff --git a/public/sass/_variables.scss b/public/sass/_variables.scss index d76111b080b..12692690772 100644 --- a/public/sass/_variables.scss +++ b/public/sass/_variables.scss @@ -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; diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss new file mode 100644 index 00000000000..5a346d443a6 --- /dev/null +++ b/public/sass/components/_dashboard_grid.scss @@ -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; + } +} + + diff --git a/public/sass/components/_gridstack.scss b/public/sass/components/_gridstack.scss deleted file mode 100644 index a3d31e631c2..00000000000 --- a/public/sass/components/_gridstack.scss +++ /dev/null @@ -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; -} diff --git a/public/sass/components/_modals.scss b/public/sass/components/_modals.scss index 2525f104aa7..14713fbda74 100644 --- a/public/sass/components/_modals.scss +++ b/public/sass/components/_modals.scss @@ -10,7 +10,7 @@ bottom: 0; left: 0; z-index: $zindex-modal-backdrop; - background-color: $black; + background-color: $modal-backdrop-bg; } .modal-backdrop, diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 4ccabb5f19a..8456af7b152 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -17,7 +17,7 @@ .sidemenu-open { .navbar { - padding-left: 0; + padding-left: $panel-margin; } } diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index 878058bd1c4..8211fffa9ee 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -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 { diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 53adcf6d825..0559f75fd91 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -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 { diff --git a/public/test/specs/helpers.d.ts b/public/test/specs/helpers.d.ts deleted file mode 100644 index 4b64bd79031..00000000000 --- a/public/test/specs/helpers.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare let helpers: any; -export default helpers; - - diff --git a/public/test/specs/helpers.js b/public/test/specs/helpers.js deleted file mode 100644 index 40c4a75423c..00000000000 --- a/public/test/specs/helpers.js +++ /dev/null @@ -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 - }; - -}); diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts new file mode 100644 index 00000000000..709617a9465 --- /dev/null +++ b/public/test/specs/helpers.ts @@ -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; diff --git a/public/test/test-main.js b/public/test/test-main.js deleted file mode 100644 index 1347c421a64..00000000000 --- a/public/test/test-main.js +++ /dev/null @@ -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); - }); - -})(); diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 037212c26de..1ed9a412fdf 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -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', diff --git a/yarn.lock b/yarn.lock index 2b5ccda06b1..326d2176eb5 100644 --- a/yarn.lock +++ b/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"