From 6cd2624caf33b35db25d032efff94231781ad70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Oct 2017 17:24:10 +0200 Subject: [PATCH 01/15] ux: initial react grid poc --- package.json | 6 +- public/app/features/dashboard/all.js | 2 +- .../dashboard/dashgrid/DashboardGrid.tsx | 96 +++++++++++++++++++ public/app/partials/dashboard.html | 3 +- public/sass/_grafana.scss | 9 +- public/sass/components/_dashboard_grid.scss | 2 + public/sass/pages/_dashboard.scss | 2 + yarn.lock | 68 ++++++++++--- 8 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/DashboardGrid.tsx create mode 100644 public/sass/components/_dashboard_grid.scss diff --git a/package.json b/package.json index a13b50bea7c..b6e10365c91 100644 --- a/package.json +++ b/package.json @@ -112,16 +112,18 @@ "brace": "^0.10.0", "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/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 388989bd442..a9c5488cad9 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -23,7 +23,7 @@ define([ './export_data/export_data_modal', './ad_hoc_filters', './repeat_option/repeat_option', - './dashgrid/dashgrid', + './dashgrid/DashboardGrid', './acl/acl', './folder_picker/picker', './folder_modal/folder' diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx new file mode 100644 index 00000000000..5a3ab3ea043 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import coreModule from 'app/core/core_module'; +import ReactGridLayout from 'react-grid-layout'; +import {DashboardModel} from '../model'; +import sizeMe from 'react-sizeme'; + +const COLUMN_COUNT = 24; +const ROW_HEIGHT = 30; + +function GridWrapper({size, layout, onLayoutChange, children}) { + 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 { + dashboard: DashboardModel; +} + +export class DashboardGrid extends React.Component { + gridToPanelMap: any; + + constructor(props) { + super(props); + this.onLayoutChange = this.onLayoutChange.bind(this); + } + + buildLayout() { + const layout = []; + for (let panel of this.props.dashboard.panels) { + layout.push({ + i: panel.id.toString(), + x: panel.x, + y: panel.y, + w: panel.width, + h: panel.height, + }); + } + console.log(layout); + return layout; + } + + onLayoutChange() {} + + renderPanels() { + const panelElements = []; + for (let panel of this.props.dashboard.panels) { + panelElements.push( +
+
+
+
{panel.type}
+
+
+ {panel.title} - {panel.type} +
+
+
, + ); + } + return panelElements; + } + + render() { + return ( + + {this.renderPanels()} + + ); + } +} + +coreModule.directive('dashboardGrid', function(reactDirective) { + return reactDirective(DashboardGrid, [['dashboard', {watchDepth: 'reference'}]]); +}); diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 16218f1916c..e93222c0456 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -8,7 +8,8 @@ - + + 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/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss new file mode 100644 index 00000000000..bf0e6657a0b --- /dev/null +++ b/public/sass/components/_dashboard_grid.scss @@ -0,0 +1,2 @@ +@import "~react-grid-layout/css/styles.css"; +@import "~react-resizable/css/styles.css"; 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/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" From 63bf2a02789949e44a95c1edaa42c75468157fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Oct 2017 17:52:25 +0200 Subject: [PATCH 02/15] grid: need to find a way to add angular component inside react --- .../dashboard/dashgrid/DashboardGrid.tsx | 10 ++------ .../dashboard/dashgrid/DashboardPanel.tsx | 24 +++++++++++++++++++ .../dashboard/dashgrid/dashboard_plugin.ts | 3 +++ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/DashboardPanel.tsx create mode 100644 public/app/features/dashboard/dashgrid/dashboard_plugin.ts diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 5a3ab3ea043..b6029f99ce7 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -2,6 +2,7 @@ import React from 'react'; import coreModule from 'app/core/core_module'; import ReactGridLayout from 'react-grid-layout'; import {DashboardModel} from '../model'; +import {DashboardPanel} from './DashboardPanel'; import sizeMe from 'react-sizeme'; const COLUMN_COUNT = 24; @@ -68,14 +69,7 @@ export class DashboardGrid extends React.Component { for (let panel of this.props.dashboard.panels) { panelElements.push(
-
-
-
{panel.type}
-
-
- {panel.title} - {panel.type} -
-
+
, ); } diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx new file mode 100644 index 00000000000..16279ff99b4 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export interface DashboardPanelProps { + panel: any; +} + +export class DashboardPanel extends React.Component { + private element: any; + + constructor(props) { + super(props); + this.state = {}; + } + + componentDidMount() { + } + + render() { + return ( +
this.element = element} /> + ); + } +} + diff --git a/public/app/features/dashboard/dashgrid/dashboard_plugin.ts b/public/app/features/dashboard/dashgrid/dashboard_plugin.ts new file mode 100644 index 00000000000..92083830c52 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/dashboard_plugin.ts @@ -0,0 +1,3 @@ +import coreModule from 'app/core/core'; + + From a867dc069b76fd5e464821095106bdd7b6bc8dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Oct 2017 20:38:48 +0200 Subject: [PATCH 03/15] tech: got angular component to load inside react grid --- public/app/features/dashboard/all.js | 1 + .../app/features/dashboard/dashboard_ctrl.ts | 7 ++-- .../dashboard/dashgrid/DashboardGrid.tsx | 11 +++++-- .../dashboard/dashgrid/DashboardPanel.tsx | 5 +++ .../dashboard/dashgrid/PanelLoader.ts | 33 +++++++++++++++++++ .../dashboard/dashgrid/dashboard_plugin.ts | 3 -- .../app/features/plugins/plugin_component.ts | 2 +- public/app/partials/dashboard.html | 3 +- 8 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/PanelLoader.ts delete mode 100644 public/app/features/dashboard/dashgrid/dashboard_plugin.ts diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index a9c5488cad9..93368e401a5 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -24,6 +24,7 @@ define([ './ad_hoc_filters', './repeat_option/repeat_option', './dashgrid/DashboardGrid', + './dashgrid/PanelLoader', './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..728bd4a8432 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -1,5 +1,3 @@ -/// - import config from 'app/core/config'; import angular from 'angular'; @@ -19,6 +17,7 @@ export class DashboardCtrl { unsavedChangesSrv, dynamicDashboardSrv, dashboardViewStateSrv, + panelLoader, contextSrv, alertSrv, $timeout) { @@ -131,6 +130,10 @@ export class DashboardCtrl { $scope.dashboard.meta.folderId = folder.id; $scope.dashboard.meta.folderTitle= folder.title; }; + + $scope.getPanelLoader = function() { + return panelLoader; + }; } init(dashboard) { diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index b6029f99ce7..fc972e7abc4 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -3,9 +3,10 @@ import coreModule from 'app/core/core_module'; import ReactGridLayout from 'react-grid-layout'; import {DashboardModel} from '../model'; import {DashboardPanel} from './DashboardPanel'; +import {PanelLoader} from './PanelLoader'; import sizeMe from 'react-sizeme'; -const COLUMN_COUNT = 24; +const COLUMN_COUNT = 12; const ROW_HEIGHT = 30; function GridWrapper({size, layout, onLayoutChange, children}) { @@ -37,6 +38,7 @@ const SizedReactLayoutGrid = sizeMe({monitorWidth: true})(GridWrapper); export interface DashboardGridProps { dashboard: DashboardModel; + getPanelLoader: () => PanelLoader; } export class DashboardGrid extends React.Component { @@ -69,7 +71,7 @@ export class DashboardGrid extends React.Component { for (let panel of this.props.dashboard.panels) { panelElements.push(
- +
, ); } @@ -86,5 +88,8 @@ export class DashboardGrid extends React.Component { } coreModule.directive('dashboardGrid', function(reactDirective) { - return reactDirective(DashboardGrid, [['dashboard', {watchDepth: 'reference'}]]); + return reactDirective(DashboardGrid, [ + ['dashboard', {watchDepth: 'reference'}], + ['getPanelLoader', {watchDepth: 'reference', wrapApply: false}], + ]); }); diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 16279ff99b4..ad9c1ccb585 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,7 +1,10 @@ import React from 'react'; +import {PanelLoader} from './PanelLoader'; export interface DashboardPanelProps { panel: any; + dashboard: any; + getPanelLoader: () => PanelLoader; } export class DashboardPanel extends React.Component { @@ -13,6 +16,8 @@ export class DashboardPanel extends React.Component { } componentDidMount() { + var loader = this.props.getPanelLoader(); + loader.load(this.element, this.props.panel, this.props.dashboard); } render() { diff --git a/public/app/features/dashboard/dashgrid/PanelLoader.ts b/public/app/features/dashboard/dashgrid/PanelLoader.ts new file mode 100644 index 00000000000..3d0988381e0 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelLoader.ts @@ -0,0 +1,33 @@ +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: () => { + panelScope.$destroy(); + compiledElem.remove(); + } + }; + } +} + +coreModule.service('panelLoader', PanelLoader); diff --git a/public/app/features/dashboard/dashgrid/dashboard_plugin.ts b/public/app/features/dashboard/dashgrid/dashboard_plugin.ts deleted file mode 100644 index 92083830c52..00000000000 --- a/public/app/features/dashboard/dashgrid/dashboard_plugin.ts +++ /dev/null @@ -1,3 +0,0 @@ -import coreModule from 'app/core/core'; - - 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 e93222c0456..ddbd4c4d326 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -9,7 +9,8 @@ - + +
From 207773e07e78094eb3bb43161bf428dd1335de1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Oct 2017 09:34:14 +0200 Subject: [PATCH 04/15] grid: minor progress --- public/app/features/dashboard/PanelModel.ts | 11 + .../app/features/dashboard/dashboard_ctrl.ts | 212 +++++++++--------- .../dashboard/dashgrid/DashboardGrid.tsx | 36 +-- .../dashboard/dashgrid/DashboardPanel.tsx | 25 ++- .../dashboard/dashgrid/PanelContainer.ts | 11 + .../dashboard/dashgrid/PanelLoader.ts | 1 + public/app/features/dashboard/model.ts | 15 +- public/app/partials/dashboard.html | 10 +- 8 files changed, 176 insertions(+), 145 deletions(-) create mode 100644 public/app/features/dashboard/PanelModel.ts create mode 100644 public/app/features/dashboard/dashgrid/PanelContainer.ts diff --git a/public/app/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts new file mode 100644 index 00000000000..90355562ec2 --- /dev/null +++ b/public/app/features/dashboard/PanelModel.ts @@ -0,0 +1,11 @@ + +export interface PanelModel { + id: number; + x: number; + y: number; + width: number; + height: number; + type: string; + title: string; +} + diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 728bd4a8432..eeb5e7e243b 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -1,146 +1,148 @@ 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 './model'; +import {PanelModel} from './PanelModel'; -export class DashboardCtrl { +export class DashboardCtrl implements PanelContainer { + dashboard: DashboardModel; + dashboardViewState: any; + loadedFallbackDashboard: boolean; /** @ngInject */ constructor( private $scope, - $rootScope, - keybindingSrv, - timeSrv, - variableSrv, - alertingSrv, - dashboardSrv, - unsavedChangesSrv, - dynamicDashboardSrv, - dashboardViewStateSrv, - panelLoader, - 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 }; + // funcs called from React component bindings and needs this binding + this.getPanelContainer = this.getPanelContainer.bind(this); + } - var resizeEventTimeout; + setupDashboard(data) { + try { + this.setupDashboardInternal(data); + } catch (err) { + this.onInitFailed(err, 'Dashboard init failed', true); + } + } - $scope.setupDashboard = function(data) { - try { - $scope.setupDashboardInternal(data); - } catch (err) { - $scope.onInitFailed(err, 'Dashboard init failed', true); - } - }; + setupDashboardInternal(data) { + const dashboard = this.dashboardSrv.create(data.dashboard, data.meta); + this.dashboardSrv.setCurrent(dashboard); - $scope.setupDashboardInternal = function(data) { - var dashboard = dashboardSrv.create(data.dashboard, data.meta); - dashboardSrv.setCurrent(dashboard); + // init services + this.timeSrv.init(dashboard); + this.alertingSrv.init(dashboard, data.alerts); - // init services - timeSrv.init(dashboard); - alertingSrv.init(dashboard, data.alerts); + // 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; - // 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(); + this.dynamicDashboardSrv.init(dashboard); + this.dynamicDashboardSrv.process(); - unsavedChangesSrv.init(dashboard, $scope); + this.unsavedChangesSrv.init(dashboard, this.$scope); - $scope.dashboard = dashboard; - $scope.dashboardMeta = dashboard.meta; - $scope.dashboardViewState = dashboardViewStateSrv.create($scope); + // TODO refactor ViewStateSrv + this.$scope.dashboard = dashboard; + this.dashboardViewState = this.dashboardViewStateSrv.create(this.$scope); - keybindingSrv.setupDashboardBindings($scope, dashboard); + this.keybindingSrv.setupDashboardBindings(this.$scope, dashboard); - $scope.dashboard.updateSubmenuVisibility(); - $scope.setWindowTitleAndTheme(); + this.dashboard.updateSubmenuVisibility(); + this.setWindowTitleAndTheme(); - $scope.appEvent("dashboard-initialized", $scope.dashboard); - }) - .catch($scope.onInitFailed.bind(this, 'Dashboard init failed', true)); - }; + this.$scope.appEvent("dashboard-initialized", dashboard); + }) + .catch(this.onInitFailed.bind(this, 'Dashboard init failed', true)); + } - $scope.onInitFailed = function(msg, fatal, err) { - console.log(msg, err); + onInitFailed(msg, fatal, err) { + console.log(msg, err); - if (err.data && err.data.message) { - err.message = err.data.message; - } else if (!err.message) { - err = {message: err.toString()}; - } + if (err.data && err.data.message) { + err.message = err.data.message; + } else if (!err.message) { + 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'}}); - } - }; + // protect against recursive fallbacks + 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'); - }; + 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.addRowDefault = function() { - $scope.dashboard.addEmptyRow(); - }; + getDashboard() { + return this.dashboard; + } - $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 }); - }; + getPanelLoader() { + return this.panelLoader; + } - $scope.registerWindowResizeEvent = function() { - angular.element(window).bind('resize', function() { - $timeout.cancel(resizeEventTimeout); - resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200); - }); + getPanels() { + return this.dashboard.panels; + } - $scope.$on('$destroy', function() { - angular.element(window).unbind('resize'); - $scope.dashboard.destroy(); - }); - }; + panelPossitionUpdated(panel: PanelModel) { + console.log('panel pos updated', panel); + } - $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; + } - $scope.getPanelLoader = function() { - return panelLoader; - }; + 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/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index fc972e7abc4..8d1d8a74a80 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -1,13 +1,12 @@ import React from 'react'; import coreModule from 'app/core/core_module'; import ReactGridLayout from 'react-grid-layout'; -import {DashboardModel} from '../model'; +import {CELL_HEIGHT, CELL_VMARGIN} from '../model'; import {DashboardPanel} from './DashboardPanel'; -import {PanelLoader} from './PanelLoader'; +import {PanelContainer} from './PanelContainer'; import sizeMe from 'react-sizeme'; const COLUMN_COUNT = 12; -const ROW_HEIGHT = 30; function GridWrapper({size, layout, onLayoutChange, children}) { if (size.width === 0) { @@ -23,9 +22,9 @@ function GridWrapper({size, layout, onLayoutChange, children}) { isDraggable={true} isResizable={true} measureBeforeMount={false} - margin={[10, 10]} + margin={[CELL_VMARGIN, CELL_VMARGIN]} cols={COLUMN_COUNT} - rowHeight={ROW_HEIGHT} + rowHeight={CELL_HEIGHT} draggableHandle=".grid-drag-handle" layout={layout} onLayoutChange={onLayoutChange}> @@ -37,21 +36,24 @@ function GridWrapper({size, layout, onLayoutChange, children}) { const SizedReactLayoutGrid = sizeMe({monitorWidth: true})(GridWrapper); export interface DashboardGridProps { - dashboard: DashboardModel; - getPanelLoader: () => PanelLoader; + getPanelContainer: () => PanelContainer; } export class DashboardGrid extends React.Component { gridToPanelMap: any; + panelContainer: PanelContainer; constructor(props) { super(props); + this.panelContainer = this.props.getPanelContainer(); this.onLayoutChange = this.onLayoutChange.bind(this); } buildLayout() { const layout = []; - for (let panel of this.props.dashboard.panels) { + const panels = this.panelContainer.getPanels(); + + for (let panel of panels) { layout.push({ i: panel.id.toString(), x: panel.x, @@ -60,21 +62,28 @@ export class DashboardGrid extends React.Component { h: panel.height, }); } - console.log(layout); + + console.log('layout', layout); return layout; } onLayoutChange() {} renderPanels() { + const panels = this.panelContainer.getPanels(); const panelElements = []; - for (let panel of this.props.dashboard.panels) { + + for (let panel of panels) { panelElements.push(
- +
, ); } + return panelElements; } @@ -88,8 +97,5 @@ export class DashboardGrid extends React.Component { } coreModule.directive('dashboardGrid', function(reactDirective) { - return reactDirective(DashboardGrid, [ - ['dashboard', {watchDepth: 'reference'}], - ['getPanelLoader', {watchDepth: 'reference', wrapApply: false}], - ]); + 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 index ad9c1ccb585..5c7f2178c96 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import {PanelLoader} from './PanelLoader'; +import {PanelModel} from '../PanelModel'; +import {PanelContainer} from './PanelContainer'; +import {AttachedPanel} from './PanelLoader'; export interface DashboardPanelProps { - panel: any; - dashboard: any; - getPanelLoader: () => PanelLoader; + panel: PanelModel; + getPanelContainer: () => PanelContainer; } export class DashboardPanel extends React.Component { - private element: any; + element: any; + attachedPanel: AttachedPanel; constructor(props) { super(props); @@ -16,8 +18,17 @@ export class DashboardPanel extends React.Component { } componentDidMount() { - var loader = this.props.getPanelLoader(); - loader.load(this.element, this.props.panel, this.props.dashboard); + 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() { diff --git a/public/app/features/dashboard/dashgrid/PanelContainer.ts b/public/app/features/dashboard/dashgrid/PanelContainer.ts new file mode 100644 index 00000000000..352e771cb41 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/PanelContainer.ts @@ -0,0 +1,11 @@ +import {PanelModel} from '../PanelModel'; +import {DashboardModel}  from '../model'; +import {PanelLoader} from './PanelLoader'; + +export interface PanelContainer { + getPanels(): PanelModel[]; + getPanelLoader(): PanelLoader; + getDashboard(): DashboardModel; + panelPossitionUpdated(panel: PanelModel); +} + diff --git a/public/app/features/dashboard/dashgrid/PanelLoader.ts b/public/app/features/dashboard/dashgrid/PanelLoader.ts index 3d0988381e0..2e9302537af 100644 --- a/public/app/features/dashboard/dashgrid/PanelLoader.ts +++ b/public/app/features/dashboard/dashgrid/PanelLoader.ts @@ -23,6 +23,7 @@ export class PanelLoader { return { destroy: () => { + console.log('AttachedPanel:Destroy, id' + panel.id); panelScope.$destroy(); compiledElem.remove(); } diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index e407a78faa4..946368040af 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -1,5 +1,3 @@ -/// - import angular from 'angular'; import moment from 'moment'; import _ from 'lodash'; @@ -8,18 +6,9 @@ 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; @@ -50,7 +39,7 @@ export class DashboardModel { events: any; editMode: boolean; folderId: number; - panels: Panel[]; + panels: PanelModel[]; constructor(data, meta?) { if (!data) { diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index ddbd4c4d326..58fea114aeb 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -1,15 +1,15 @@ -
- +
+
- + + - - +
From 10a35043092dafdc450f5cfbdaa27c91d607c751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Oct 2017 14:20:53 +0200 Subject: [PATCH 05/15] grid: progress on new grid, resize & saving layouts works --- public/app/features/dashboard/PanelModel.ts | 59 ++++- .../app/features/dashboard/dashboard_ctrl.ts | 3 +- .../dashboard/dashgrid/DashboardGrid.tsx | 35 ++- .../features/dashboard/dashgrid/dashgrid.ts | 211 ---------------- .../app/features/dashboard/dashgrid/ref.txt | 232 ------------------ public/app/features/dashboard/model.ts | 69 +++--- .../app/features/dashboard/save_as_modal.ts | 8 +- public/app/features/panel/panel_ctrl.ts | 28 +-- public/app/features/panel/panel_directive.ts | 1 + public/app/features/panel/panel_header.ts | 10 +- public/app/plugins/panel/graph/graph.ts | 1 + public/dashboards/home.json | 30 ++- 12 files changed, 150 insertions(+), 537 deletions(-) delete mode 100644 public/app/features/dashboard/dashgrid/dashgrid.ts delete mode 100644 public/app/features/dashboard/dashgrid/ref.txt diff --git a/public/app/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts index 90355562ec2..592c21f29fe 100644 --- a/public/app/features/dashboard/PanelModel.ts +++ b/public/app/features/dashboard/PanelModel.ts @@ -1,11 +1,62 @@ +import {Emitter} from 'app/core/core'; -export interface PanelModel { - id: number; +export interface GridPos { x: number; y: number; - width: number; - height: number; + w: number; + h: number; +} + +const notPersistedProperties: {[str: string]: boolean} = { + "model": true, + "events": true, +}; + +export class PanelModel { + id: number; + gridPos: GridPos; type: string; title: string; + events: Emitter; + + constructor(private model) { + // copy properties from persisted model + for (var property in model) { + this[property] = model[property]; + } + + this.events = new Emitter(); + } + + getSaveModel() { + this.model = {}; + for (var property in this) { + if (notPersistedProperties[property] || !this.hasOwnProperty(property)) { + console.log('PanelModel.getSaveModel() skiping property', property); + continue; + } + + this.model[property] = this[property]; + } + return this.model; + } + + 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/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index eeb5e7e243b..98015d666af 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -121,7 +121,8 @@ export class DashboardCtrl implements PanelContainer { } panelPossitionUpdated(panel: PanelModel) { - console.log('panel pos updated', panel); + //console.log('panel pos updated', panel); + //this.$rootScope.$broadcast('render'); } timezoneChanged() { diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 8d1d8a74a80..93b02c82395 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -4,6 +4,7 @@ import ReactGridLayout from 'react-grid-layout'; import {CELL_HEIGHT, CELL_VMARGIN} from '../model'; import {DashboardPanel} from './DashboardPanel'; import {PanelContainer} from './PanelContainer'; +import {PanelModel} from '../PanelModel'; import sizeMe from 'react-sizeme'; const COLUMN_COUNT = 12; @@ -22,6 +23,8 @@ function GridWrapper({size, layout, onLayoutChange, children}) { isDraggable={true} isResizable={true} measureBeforeMount={false} + containerPadding={[0, 0]} + useCSSTransforms={true} margin={[CELL_VMARGIN, CELL_VMARGIN]} cols={COLUMN_COUNT} rowHeight={CELL_HEIGHT} @@ -42,6 +45,7 @@ export interface DashboardGridProps { export class DashboardGrid extends React.Component { gridToPanelMap: any; panelContainer: PanelContainer; + panelMap: {[id: string]: PanelModel}; constructor(props) { super(props); @@ -52,22 +56,34 @@ export class DashboardGrid extends React.Component { buildLayout() { const layout = []; const panels = this.panelContainer.getPanels(); + this.panelMap = {}; for (let panel of panels) { + let stringId = panel.id.toString(); + this.panelMap[stringId] = panel; + + if (!panel.gridPos) { + console.log('panel without gridpos'); + continue; + } + layout.push({ - i: panel.id.toString(), - x: panel.x, - y: panel.y, - w: panel.width, - h: panel.height, + i: stringId, + x: panel.gridPos.x, + y: panel.gridPos.y, + w: panel.gridPos.w, + h: panel.gridPos.h, }); } - console.log('layout', layout); return layout; } - onLayoutChange() {} + onLayoutChange(newLayout) { + for (const newPos of newLayout) { + this.panelMap[newPos.i].updateGridPos(newPos); + } + } renderPanels() { const panels = this.panelContainer.getPanels(); @@ -76,10 +92,7 @@ export class DashboardGrid extends React.Component { for (let panel of panels) { panelElements.push(
- +
, ); } 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/model.ts b/public/app/features/dashboard/model.ts index 946368040af..c467868c789 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -69,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); @@ -123,34 +122,32 @@ 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})); - } - private ensureListExist(data) { if (!data) { data = {}; } if (!data.list) { data.list = []; } @@ -327,7 +324,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; @@ -636,7 +633,7 @@ export class DashboardModel { this.graphTooltip = old.sharedCrosshair ? 1 : 0; } - if (oldVersion < 15) { + if (oldVersion < 16) { this.upgradeToGridLayout(old); } @@ -644,60 +641,54 @@ 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; for (let row of old.rows) { let xPos = 0; let height: any = row.height; - 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/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/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 9806be39273..8d927d79b53 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -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(); + }); + } + 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/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 723a92ad19b..49ed254bfb1 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -54,6 +54,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) { }); ctrl.events.on('render', function(renderData) { + console.log('graph render'); data = renderData || data; if (!data) { return; diff --git a/public/dashboards/home.json b/public/dashboards/home.json index d19ecfd6809..e2da07be3d6 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,22 +41,27 @@ "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": [], From 2e77bd0cb1098e1a4a451e92a8678952ab3e24a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Oct 2017 17:57:53 +0200 Subject: [PATCH 06/15] grid: progress on react grid --- public/app/core/directives/dash_edit_link.js | 2 +- public/app/features/dashboard/PanelModel.ts | 15 +++++------ public/app/features/dashboard/all.js | 1 + .../app/features/dashboard/dashboard_ctrl.ts | 14 +++------- .../dashboard/dashgrid/DashboardGrid.tsx | 27 ++++++++++++++----- .../dashboard/dashgrid/PanelContainer.ts | 3 --- .../features/dashboard/dashnav/dashnav.html | 2 +- .../app/features/dashboard/dashnav/dashnav.ts | 8 ++++++ public/app/features/dashboard/model.ts | 11 +++++++- .../features/dashboard/partials/settings.html | 26 +++++++++--------- public/app/features/panel/panel_ctrl.ts | 2 +- public/sass/_variables.dark.scss | 6 ++--- public/sass/_variables.light.scss | 6 ++--- public/sass/components/_modals.scss | 2 +- 14 files changed, 74 insertions(+), 51 deletions(-) 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/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts index 592c21f29fe..f5455dee5ca 100644 --- a/public/app/features/dashboard/PanelModel.ts +++ b/public/app/features/dashboard/PanelModel.ts @@ -8,7 +8,6 @@ export interface GridPos { } const notPersistedProperties: {[str: string]: boolean} = { - "model": true, "events": true, }; @@ -19,26 +18,26 @@ export class PanelModel { title: string; events: Emitter; - constructor(private model) { + constructor(model) { + this.events = new Emitter(); + // copy properties from persisted model for (var property in model) { this[property] = model[property]; } - - this.events = new Emitter(); } getSaveModel() { - this.model = {}; + const model: any = {}; for (var property in this) { if (notPersistedProperties[property] || !this.hasOwnProperty(property)) { - console.log('PanelModel.getSaveModel() skiping property', property); continue; } - this.model[property] = this[property]; + model[property] = this[property]; } - return this.model; + + return model; } updateGridPos(newPos: GridPos) { diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 93368e401a5..3871dc3ff85 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -25,6 +25,7 @@ define([ './repeat_option/repeat_option', './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 98015d666af..02c2825a844 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -3,12 +3,12 @@ import config from 'app/core/config'; import coreModule from 'app/core/core_module'; import {PanelContainer} from './dashgrid/PanelContainer'; import {DashboardModel} from './model'; -import {PanelModel} from './PanelModel'; export class DashboardCtrl implements PanelContainer { dashboard: DashboardModel; dashboardViewState: any; loadedFallbackDashboard: boolean; + editTab: number; /** @ngInject */ constructor( @@ -27,6 +27,9 @@ export class DashboardCtrl implements PanelContainer { // can't use controllerAs on route yet $scope.ctrl = this; + // TODO: break out settings view to separate view & controller + this.editTab = 0; + // funcs called from React component bindings and needs this binding this.getPanelContainer = this.getPanelContainer.bind(this); } @@ -116,15 +119,6 @@ export class DashboardCtrl implements PanelContainer { return this.panelLoader; } - getPanels() { - return this.dashboard.panels; - } - - panelPossitionUpdated(panel: PanelModel) { - //console.log('panel pos updated', panel); - //this.$rootScope.$broadcast('render'); - } - timezoneChanged() { this.$rootScope.$broadcast("refresh"); } diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 93b02c82395..ca819739048 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -3,13 +3,14 @@ import coreModule from 'app/core/core_module'; import ReactGridLayout from 'react-grid-layout'; import {CELL_HEIGHT, CELL_VMARGIN} from '../model'; import {DashboardPanel} from './DashboardPanel'; +import {DashboardModel} from '../model'; import {PanelContainer} from './PanelContainer'; import {PanelModel} from '../PanelModel'; import sizeMe from 'react-sizeme'; const COLUMN_COUNT = 12; -function GridWrapper({size, layout, onLayoutChange, children}) { +function GridWrapper({size, layout, onLayoutChange, children, onResize}) { if (size.width === 0) { console.log('size is zero!'); } @@ -30,6 +31,7 @@ function GridWrapper({size, layout, onLayoutChange, children}) { rowHeight={CELL_HEIGHT} draggableHandle=".grid-drag-handle" layout={layout} + onResize={onResize} onLayoutChange={onLayoutChange}> {children} @@ -45,20 +47,25 @@ export interface DashboardGridProps { 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.panelAdded.bind(this)); } buildLayout() { const layout = []; - const panels = this.panelContainer.getPanels(); this.panelMap = {}; - for (let panel of panels) { + for (let panel of this.dashboard.panels) { let stringId = panel.id.toString(); this.panelMap[stringId] = panel; @@ -85,11 +92,18 @@ export class DashboardGrid extends React.Component { } } + panelAdded() { + this.forceUpdate(); + } + + onResize(layout, oldItem, newItem) { + this.panelMap[newItem.i].updateGridPos(newItem); + } + renderPanels() { - const panels = this.panelContainer.getPanels(); const panelElements = []; - for (let panel of panels) { + for (let panel of this.dashboard.panels) { panelElements.push(
@@ -101,8 +115,9 @@ export class DashboardGrid extends React.Component { } render() { + console.log('DashboardGrid.render()'); return ( - + {this.renderPanels()} ); diff --git a/public/app/features/dashboard/dashgrid/PanelContainer.ts b/public/app/features/dashboard/dashgrid/PanelContainer.ts index 352e771cb41..39f589f44eb 100644 --- a/public/app/features/dashboard/dashgrid/PanelContainer.ts +++ b/public/app/features/dashboard/dashgrid/PanelContainer.ts @@ -1,11 +1,8 @@ -import {PanelModel} from '../PanelModel'; import {DashboardModel}  from '../model'; import {PanelLoader} from './PanelLoader'; export interface PanelContainer { - getPanels(): PanelModel[]; getPanelLoader(): PanelLoader; getDashboard(): DashboardModel; - panelPossitionUpdated(panel: PanelModel); } 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..b197c48c649 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -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/model.ts b/public/app/features/dashboard/model.ts index c467868c789..4ab7dfbec59 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -180,7 +180,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?) { @@ -282,6 +283,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; } 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/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 8d927d79b53..35ced744378 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -178,7 +178,7 @@ export class PanelCtrl { this.calculatePanelHeight(); this.$timeout(() => { this.render(); - }); + }, 100); } duplicate() { 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/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, From df6155801869caa8de2d485c7b87f8e292eb3538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Oct 2017 19:48:06 +0200 Subject: [PATCH 07/15] grid: fixed geting started panel pos --- pkg/api/dashboard.go | 14 ++++++++------ public/dashboards/home.json | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) 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/public/dashboards/home.json b/public/dashboards/home.json index e2da07be3d6..91f5abec5eb 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -65,7 +65,7 @@ } ], "rows": [], - "schemaVersion": 15, + "schemaVersion": 16, "style": "dark", "tags": [], "templating": { From cd6a18ec620be4b044acc6c4a457095fd4aeacd4 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 10 Oct 2017 20:56:05 +0200 Subject: [PATCH 08/15] search: fix search to limit dashboards better Change the query to first select the dashboards, apply the limit then join with tags. Means the limit will apply to the number of dashboards returned in the search. The disadvantage is that the query will return more rows than the limit, no. of dashboards x no. of tags. So hard limit set to 5000 for all rows. --- pkg/services/sqlstore/dashboard.go | 94 +++++++++++++++--------- pkg/services/sqlstore/dashboard_test.go | 4 +- pkg/services/sqlstore/datasource_test.go | 5 +- 3 files changed, 65 insertions(+), 38 deletions(-) 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 { From 087b55aece36d8b04ba8ed297b9c1d262bf529fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Oct 2017 11:42:49 +0200 Subject: [PATCH 09/15] newgrid: progress on fullscreen/edit view modes --- package.json | 1 + public/app/core/directives/dash_class.js | 4 +- .../dashboard/{model.ts => DashboardModel.ts} | 11 +- public/app/features/dashboard/PanelModel.ts | 11 + .../app/features/dashboard/dashboard_ctrl.ts | 2 +- .../app/features/dashboard/dashboard_srv.ts | 4 +- .../dashboard/dashgrid/DashboardGrid.tsx | 8 +- .../dashboard/dashgrid/PanelContainer.ts | 2 +- .../app/features/dashboard/dashnav/dashnav.ts | 2 +- .../app/features/dashboard/history/history.ts | 2 +- .../features/dashboard/history/history_srv.ts | 4 +- .../dashboard/specs/dashboard_model_specs.ts | 2 +- .../dashboard/specs/exporter_specs.ts | 2 +- public/app/features/dashboard/viewStateSrv.js | 13 +- public/app/features/panel/metrics_tab.ts | 4 +- public/app/features/panel/panel_ctrl.ts | 6 +- public/sass/_variables.scss | 4 +- public/sass/components/_dashboard_grid.scss | 4 + public/sass/components/_gridstack.scss | 325 ------------------ public/sass/components/_navbar.scss | 2 +- public/sass/components/_sidemenu.scss | 1 + public/sass/components/_submenu.scss | 2 +- 22 files changed, 51 insertions(+), 365 deletions(-) rename public/app/features/dashboard/{model.ts => DashboardModel.ts} (98%) delete mode 100644 public/sass/components/_gridstack.scss diff --git a/package.json b/package.json index b6e10365c91..b34ceb1bce3 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "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", "file-saver": "^1.3.3", 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/features/dashboard/model.ts b/public/app/features/dashboard/DashboardModel.ts similarity index 98% rename from public/app/features/dashboard/model.ts rename to public/app/features/dashboard/DashboardModel.ts index 4ab7dfbec59..45141d57534 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/DashboardModel.ts @@ -10,7 +10,7 @@ import {PanelModel} from './PanelModel'; import sortByKeys from 'app/core/utils/sort_by_keys'; export const CELL_HEIGHT = 30; -export const CELL_VMARGIN = 15; +export const CELL_VMARGIN = 10; export class DashboardModel { id: any; @@ -148,6 +148,15 @@ export class DashboardModel { return copy; } + 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) { if (!data) { data = {}; } if (!data.list) { data.list = []; } diff --git a/public/app/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts index f5455dee5ca..0bb1ada986f 100644 --- a/public/app/features/dashboard/PanelModel.ts +++ b/public/app/features/dashboard/PanelModel.ts @@ -9,6 +9,8 @@ export interface GridPos { const notPersistedProperties: {[str: string]: boolean} = { "events": true, + "fullscreen": true, + "isEditing": true, }; export class PanelModel { @@ -16,6 +18,10 @@ export class PanelModel { gridPos: GridPos; type: string; title: string; + + // non persisted + fullscreen: boolean; + isEditing: boolean; events: Emitter; constructor(model) { @@ -40,6 +46,11 @@ export class PanelModel { return model; } + setViewMode(fullscreen: boolean, isEditing: boolean) { + this.fullscreen = fullscreen; + this.isEditing = isEditing; + } + updateGridPos(newPos: GridPos) { let sizeChanged = false; diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 02c2825a844..b457f6d33c0 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -2,7 +2,7 @@ import config from 'app/core/config'; import coreModule from 'app/core/core_module'; import {PanelContainer} from './dashgrid/PanelContainer'; -import {DashboardModel} from './model'; +import {DashboardModel} from './DashboardModel'; export class DashboardCtrl implements PanelContainer { dashboard: DashboardModel; 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 index ca819739048..da75231d0aa 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -1,11 +1,12 @@ import React from 'react'; import coreModule from 'app/core/core_module'; import ReactGridLayout from 'react-grid-layout'; -import {CELL_HEIGHT, CELL_VMARGIN} from '../model'; +import {CELL_HEIGHT, CELL_VMARGIN} from '../DashboardModel'; import {DashboardPanel} from './DashboardPanel'; -import {DashboardModel} from '../model'; +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; @@ -104,8 +105,9 @@ export class DashboardGrid extends React.Component { const panelElements = []; for (let panel of this.dashboard.panels) { + const panelClasses = classNames({panel: true, 'panel--fullscreen': panel.fullscreen}); panelElements.push( -
+
, ); diff --git a/public/app/features/dashboard/dashgrid/PanelContainer.ts b/public/app/features/dashboard/dashgrid/PanelContainer.ts index 39f589f44eb..e2e524ed8da 100644 --- a/public/app/features/dashboard/dashgrid/PanelContainer.ts +++ b/public/app/features/dashboard/dashgrid/PanelContainer.ts @@ -1,4 +1,4 @@ -import {DashboardModel}  from '../model'; +import {DashboardModel}  from '../DashboardModel'; import {PanelLoader} from './PanelLoader'; export interface PanelContainer { diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index b197c48c649..d729d4f88bd 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; 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/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..c9364ceb421 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -27,8 +27,8 @@ function (angular, _, $, config) { } }); - $scope.onAppEvent('panel-change-view', function(evt, payload) { - self.update(payload); + self.dashboard.on('view-mode-changed', function(panel) { + self.update({fullscreen: panel.fullscreen, edit: panel.isEditing}); }); $scope.onAppEvent('panel-initialized', function(evt, payload) { @@ -154,9 +154,6 @@ function (angular, _, $, config) { ctrl.editMode = false; ctrl.fullscreen = false; - ctrl.dashboard.editMode = this.oldDashboardEditMode; - - this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id}); if (!render) { return false;} @@ -176,14 +173,8 @@ 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.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id}); $timeout(function() { ctrl.render(); 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 35ced744378..2d9706aa096 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; @@ -72,9 +72,7 @@ export class PanelCtrl { } changeView(fullscreen, edit) { - this.publishAppEvent('panel-change-view', { - fullscreen: fullscreen, edit: edit, panelId: this.panel.id - }); + this.dashboard.setViewMode(this.panel, fullscreen, edit); } viewPanel() { 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 index bf0e6657a0b..46d794e6545 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -1,2 +1,6 @@ @import "~react-grid-layout/css/styles.css"; @import "~react-resizable/css/styles.css"; + +.panel-in-fullscreen { + +} 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/_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/_sidemenu.scss b/public/sass/components/_sidemenu.scss index 86011f41c28..0c16a7421b2 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -5,6 +5,7 @@ flex-direction: column; width: $side-menu-width; background: $navbarBackground; + overflow: hidden; z-index: 10; a:focus { 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 { From 18f58ea36c1aecc88a80d2cb88a8966c52c24e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Oct 2017 13:52:36 +0200 Subject: [PATCH 10/15] ux: minor fix sidemenu --- public/sass/components/_sidemenu.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/public/sass/components/_sidemenu.scss b/public/sass/components/_sidemenu.scss index 0c16a7421b2..86011f41c28 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -5,7 +5,6 @@ flex-direction: column; width: $side-menu-width; background: $navbarBackground; - overflow: hidden; z-index: 10; a:focus { From d8e4decc60a6976a8a68ead37086addbab268e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Oct 2017 14:36:25 +0200 Subject: [PATCH 11/15] grid: fixed migration for rows without height --- .../app/features/dashboard/DashboardModel.ts | 2 +- public/app/features/dashboard/all.js | 1 - .../dashboard/dashgrid/DashboardGrid.tsx | 1 - .../features/dashboard/graphiteImportCtrl.js | 95 ------------------- .../plugins/panel/graph/specs/graph_specs.ts | 2 +- 5 files changed, 2 insertions(+), 99 deletions(-) delete mode 100644 public/app/features/dashboard/graphiteImportCtrl.js diff --git a/public/app/features/dashboard/DashboardModel.ts b/public/app/features/dashboard/DashboardModel.ts index 45141d57534..a036e418c4b 100644 --- a/public/app/features/dashboard/DashboardModel.ts +++ b/public/app/features/dashboard/DashboardModel.ts @@ -672,7 +672,7 @@ export class DashboardModel { 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({ diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 3871dc3ff85..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', diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index da75231d0aa..7cf0cb190ea 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -117,7 +117,6 @@ export class DashboardGrid extends React.Component { } render() { - console.log('DashboardGrid.render()'); return ( {this.renderPanels()} 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/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 1c240bb2967..a4f74e395ae 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: [ From 9785e6b6304c399b100875c24c2e5dbed83e8e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Oct 2017 14:40:18 +0200 Subject: [PATCH 12/15] fix: ignore upgrading dashboard grid when there are no rows --- public/app/features/dashboard/DashboardModel.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/app/features/dashboard/DashboardModel.ts b/public/app/features/dashboard/DashboardModel.ts index a036e418c4b..ee057722869 100644 --- a/public/app/features/dashboard/DashboardModel.ts +++ b/public/app/features/dashboard/DashboardModel.ts @@ -669,6 +669,11 @@ export class DashboardModel { upgradeToGridLayout(old) { let yPos = 0; //let rowIds = 1000; + // + + if (!old.rows) { + return; + } for (let row of old.rows) { let xPos = 0; From 5e8f253ff8a82b56b8e1ba1577e5b89475608f37 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 11 Oct 2017 16:07:10 +0200 Subject: [PATCH 13/15] webpack: changed devtools setting to stop exceptions --- scripts/webpack/webpack.dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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', From 0f6270098b483e355768eec0be0382f049456302 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 11 Oct 2017 16:08:56 +0200 Subject: [PATCH 14/15] dashboard: fixes for panels without rows --- public/app/core/services/backend_srv.ts | 80 +++++++++++-------- public/app/features/dashboard/PanelModel.ts | 1 + .../app/features/dashboard/dashnav/dashnav.ts | 12 +-- 3 files changed, 53 insertions(+), 40 deletions(-) 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/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts index 0bb1ada986f..2df26ed55f7 100644 --- a/public/app/features/dashboard/PanelModel.ts +++ b/public/app/features/dashboard/PanelModel.ts @@ -18,6 +18,7 @@ export class PanelModel { gridPos: GridPos; type: string; title: string; + alert?: any; // non persisted fullscreen: boolean; diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index d729d4f88bd..4b2587d8ed3 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -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', { From 331b50dcaa1dc456e05cab2f383c66a47e939e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 Oct 2017 16:32:05 +0200 Subject: [PATCH 15/15] grid: edit/view now works --- public/app/features/dashboard/PanelModel.ts | 1 + .../dashboard/dashgrid/DashboardGrid.tsx | 5 +- public/app/features/dashboard/viewStateSrv.js | 19 +- public/app/features/panel/panel_ctrl.ts | 4 +- public/app/plugins/panel/graph/graph.ts | 1 - .../plugins/panel/graph/specs/graph_specs.ts | 2 +- public/sass/components/_dashboard_grid.scss | 18 ++ public/test/specs/helpers.d.ts | 4 - public/test/specs/helpers.js | 180 ---------------- public/test/specs/helpers.ts | 195 ++++++++++++++++++ public/test/test-main.js | 130 ------------ 11 files changed, 232 insertions(+), 327 deletions(-) delete mode 100644 public/test/specs/helpers.d.ts delete mode 100644 public/test/specs/helpers.js create mode 100644 public/test/specs/helpers.ts delete mode 100644 public/test/test-main.js diff --git a/public/app/features/dashboard/PanelModel.ts b/public/app/features/dashboard/PanelModel.ts index 0bb1ada986f..8ad04f9f220 100644 --- a/public/app/features/dashboard/PanelModel.ts +++ b/public/app/features/dashboard/PanelModel.ts @@ -49,6 +49,7 @@ export class PanelModel { setViewMode(fullscreen: boolean, isEditing: boolean) { this.fullscreen = fullscreen; this.isEditing = isEditing; + this.events.emit('panel-size-changed'); } updateGridPos(newPos: GridPos) { diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 7cf0cb190ea..7f1958836ff 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -59,7 +59,8 @@ export class DashboardGrid extends React.Component { // subscribe to dashboard events this.dashboard = this.panelContainer.getDashboard(); - this.dashboard.on('panel-added', this.panelAdded.bind(this)); + this.dashboard.on('panel-added', this.triggerForceUpdate.bind(this)); + this.dashboard.on('view-mode-changed', this.triggerForceUpdate.bind(this)); } buildLayout() { @@ -93,7 +94,7 @@ export class DashboardGrid extends React.Component { } } - panelAdded() { + triggerForceUpdate() { this.forceUpdate(); } diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index c9364ceb421..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 @@ -27,8 +27,8 @@ function (angular, _, $, config) { } }); - self.dashboard.on('view-mode-changed', function(panel) { - self.update({fullscreen: panel.fullscreen, edit: panel.isEditing}); + $scope.onAppEvent('panel-change-view', function(evt, payload) { + self.update(payload); }); $scope.onAppEvent('panel-initialized', function(evt, payload) { @@ -155,13 +155,17 @@ function (angular, _, $, config) { ctrl.editMode = false; ctrl.fullscreen = false; + this.dashboard.setViewMode(ctrl.panel, false, false); + + this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id}); + if (!render) { return false;} $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,9 +180,8 @@ function (angular, _, $, config) { this.oldTimeRange = ctrl.range; this.fullscreenPanel = panelScope; - $timeout(function() { - ctrl.render(); - }); + this.dashboard.setViewMode(ctrl.panel, true, ctrl.editMode); + this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id}); }; DashboardViewState.prototype.registerPanel = function(panelScope) { diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 2d9706aa096..83d43a280f7 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -72,7 +72,9 @@ export class PanelCtrl { } changeView(fullscreen, edit) { - this.dashboard.setViewMode(this.panel, fullscreen, edit); + this.publishAppEvent('panel-change-view', { + fullscreen: fullscreen, edit: edit, panelId: this.panel.id + }); } viewPanel() { diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 49ed254bfb1..723a92ad19b 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -54,7 +54,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) { }); ctrl.events.on('render', function(renderData) { - console.log('graph render'); data = renderData || data; if (!data) { return; diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index a4f74e395ae..11560e71c47 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -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/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 46d794e6545..5a346d443a6 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -3,4 +3,22 @@ .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/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); - }); - -})();