Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
d5023d00
Commit
d5023d00
authored
Dec 04, 2017
by
Johannes Schill
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' of
https://github.com/grafana/grafana
into develop
parents
ba3a81ab
d29c695d
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
392 additions
and
68 deletions
+392
-68
public/app/core/components/grafana_app.ts
+10
-0
public/app/core/components/sidemenu/sidemenu.html
+6
-0
public/app/core/components/sidemenu/sidemenu.ts
+10
-0
public/app/core/services/context_srv.ts
+7
-2
public/app/features/dashboard/dashboard_model.ts
+131
-36
public/app/features/dashboard/specs/repeat.jest.ts
+133
-27
public/sass/components/_sidemenu.scss
+95
-3
No files found.
public/app/core/components/grafana_app.ts
View file @
d5023d00
...
...
@@ -85,6 +85,16 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
}
});
let
sidemenuOpenSmallBreakpoint
=
scope
.
contextSrv
.
sidemenuSmallBreakpoint
;
body
.
toggleClass
(
'sidemenu-open--xs'
,
sidemenuOpenSmallBreakpoint
);
scope
.
$watch
(
'contextSrv.sidemenuSmallBreakpoint'
,
newVal
=>
{
if
(
sidemenuOpenSmallBreakpoint
!==
scope
.
contextSrv
.
sidemenuSmallBreakpoint
)
{
sidemenuOpenSmallBreakpoint
=
scope
.
contextSrv
.
sidemenuSmallBreakpoint
;
body
.
toggleClass
(
'sidemenu-open--xs'
,
scope
.
contextSrv
.
sidemenuSmallBreakpoint
);
}
});
// tooltip removal fix
// manage page classes
var
pageClass
;
...
...
public/app/core/components/sidemenu/sidemenu.html
View file @
d5023d00
...
...
@@ -2,6 +2,12 @@
<img
src=
"public/img/grafana_icon.svg"
></img>
</a>
<a
class=
"sidemenu__logo_small_breakpoint"
ng-click=
"ctrl.toggleSideMenuSmallBreakpoint()"
>
<img
src=
"public/img/grafana_icon.svg"
></img>
<p
class=
"sidemenu__close"
><i
class=
"fa fa-times"
></i>
Close
</p>
</a>
<div
class=
"sidemenu__top"
>
<div
ng-repeat=
"item in ::ctrl.mainLinks"
class=
"sidemenu-item dropdown"
>
<a
href=
"{{::item.url}}"
class=
"sidemenu-link"
target=
"{{::item.target}}"
>
...
...
public/app/core/components/sidemenu/sidemenu.ts
View file @
d5023d00
...
...
@@ -11,6 +11,7 @@ export class SideMenuCtrl {
bottomNav
:
any
;
loginUrl
:
string
;
isSignedIn
:
boolean
;
smallBPSideMenuOpen
=
false
;
/** @ngInject */
constructor
(
private
$scope
,
private
$rootScope
,
private
$location
,
private
contextSrv
,
private
$timeout
)
{
...
...
@@ -28,6 +29,10 @@ export class SideMenuCtrl {
}
this
.
$scope
.
$on
(
'$routeChangeSuccess'
,
()
=>
{
if
(
this
.
smallBPSideMenuOpen
)
{
this
.
contextSrv
.
setSideMenuForSmallBreakpoint
(
false
,
true
);
this
.
smallBPSideMenuOpen
=
false
;
}
this
.
loginUrl
=
'login?redirect='
+
encodeURIComponent
(
this
.
$location
.
path
());
});
}
...
...
@@ -39,6 +44,11 @@ export class SideMenuCtrl {
});
}
toggleSideMenuSmallBreakpoint
()
{
this
.
smallBPSideMenuOpen
=
!
this
.
smallBPSideMenuOpen
;
this
.
contextSrv
.
setSideMenuForSmallBreakpoint
(
this
.
smallBPSideMenuOpen
,
false
);
}
switchOrg
()
{
this
.
$rootScope
.
appEvent
(
'show-modal'
,
{
templateHtml
:
'<org-switcher dismiss="dismiss()"></org-switcher>'
,
...
...
public/app/core/services/context_srv.ts
View file @
d5023d00
...
...
@@ -27,9 +27,10 @@ export class ContextSrv {
isGrafanaAdmin
:
any
;
isEditor
:
any
;
sidemenu
:
any
;
sidemenuSmallBreakpoint
=
false
;
constructor
()
{
this
.
sidemenu
=
store
.
getBool
(
'grafana.sidemenu'
,
fals
e
);
this
.
sidemenu
=
store
.
getBool
(
'grafana.sidemenu'
,
tru
e
);
if
(
!
config
.
buildInfo
)
{
config
.
buildInfo
=
{};
...
...
@@ -55,7 +56,11 @@ export class ContextSrv {
toggleSideMenu
()
{
this
.
sidemenu
=
!
this
.
sidemenu
;
store
.
set
(
'grafana.sidemenu'
,
this
.
sidemenu
);
store
.
set
(
'grafana.sidemenu'
,
this
.
sidemenu
);
}
setSideMenuForSmallBreakpoint
(
show
:
boolean
,
persistToggle
:
boolean
)
{
this
.
sidemenuSmallBreakpoint
=
show
;
}
}
...
...
public/app/features/dashboard/dashboard_model.ts
View file @
d5023d00
...
...
@@ -181,6 +181,14 @@ export class DashboardModel {
if
(
panel
.
id
>
max
)
{
max
=
panel
.
id
;
}
if
(
panel
.
collapsed
)
{
for
(
let
rowPanel
of
panel
.
panels
)
{
if
(
rowPanel
.
id
>
max
)
{
max
=
rowPanel
.
id
;
}
}
}
}
return
max
+
1
;
...
...
@@ -251,16 +259,6 @@ export class DashboardModel {
}
}
// for (let panel of this.panels) {
// if (panel.repeat) {
// if (!cleanUpOnly) {
// this.repeatPanel(panel);
// }
// } else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
// panelsToRemove.push(panel);
// }
// }
// remove panels
_
.
pull
(
this
.
panels
,
...
panelsToRemove
);
...
...
@@ -274,21 +272,11 @@ export class DashboardModel {
return
sourcePanel
;
}
var
clone
=
new
PanelModel
(
sourcePanel
.
getSaveModel
());
let
clone
=
new
PanelModel
(
sourcePanel
.
getSaveModel
());
clone
.
id
=
this
.
getNextPanelId
();
if
(
sourcePanel
.
type
===
'row'
)
{
// for row clones we need to figure out panels under row to clone and where to insert clone
let
rowPanels
=
this
.
getRowPanels
(
sourcePanelIndex
);
clone
.
panels
=
_
.
map
(
rowPanels
,
panel
=>
panel
.
getSaveModel
());
// insert after preceding row's panels
let
insertPos
=
sourcePanelIndex
+
((
rowPanels
.
length
+
1
)
*
valueIndex
);
this
.
panels
.
splice
(
insertPos
,
0
,
clone
);
}
else
{
// insert after source panel + value index
this
.
panels
.
splice
(
sourcePanelIndex
+
valueIndex
,
0
,
clone
);
}
clone
.
repeatIteration
=
this
.
iteration
;
clone
.
repeatPanelId
=
sourcePanel
.
id
;
...
...
@@ -296,37 +284,60 @@ export class DashboardModel {
return
clone
;
}
getBottomYForRow
()
{
getRowRepeatClone
(
sourcePanel
,
valueIndex
,
sourcePanelIndex
)
{
// if first clone return source
if
(
valueIndex
===
0
)
{
if
(
!
sourcePanel
.
collapsed
)
{
let
rowPanels
=
this
.
getRowPanels
(
sourcePanelIndex
);
sourcePanel
.
panels
=
rowPanels
;
}
return
sourcePanel
;
}
let
clone
=
new
PanelModel
(
sourcePanel
.
getSaveModel
());
// for row clones we need to figure out panels under row to clone and where to insert clone
let
rowPanels
,
insertPos
;
if
(
sourcePanel
.
collapsed
)
{
rowPanels
=
_
.
cloneDeep
(
sourcePanel
.
panels
);
clone
.
panels
=
rowPanels
;
// insert copied row after preceding row
insertPos
=
sourcePanelIndex
+
valueIndex
;
}
else
{
rowPanels
=
this
.
getRowPanels
(
sourcePanelIndex
);
clone
.
panels
=
_
.
map
(
rowPanels
,
panel
=>
panel
.
getSaveModel
());
// insert copied row after preceding row's panels
insertPos
=
sourcePanelIndex
+
((
rowPanels
.
length
+
1
)
*
valueIndex
);
}
this
.
panels
.
splice
(
insertPos
,
0
,
clone
);
this
.
updateRepeatedPanelIds
(
clone
);
return
clone
;
}
repeatPanel
(
panel
:
PanelModel
,
panelIndex
:
number
)
{
var
variable
=
_
.
find
(
this
.
templating
.
list
,
{
name
:
panel
.
repeat
});
let
variable
=
_
.
find
(
this
.
templating
.
list
,
{
name
:
panel
.
repeat
});
if
(
!
variable
)
{
return
;
}
var
selected
;
if
(
variable
.
current
.
text
===
'All'
)
{
selected
=
variable
.
options
.
slice
(
1
,
variable
.
options
.
length
);
}
else
{
selected
=
_
.
filter
(
variable
.
options
,
{
selected
:
true
});
if
(
panel
.
type
===
'row'
)
{
this
.
repeatRow
(
panel
,
panelIndex
,
variable
);
return
;
}
let
selectedOptions
=
this
.
getSelectedVariableOptions
(
variable
);
let
minWidth
=
panel
.
minSpan
||
6
;
let
xPos
=
0
;
let
yPos
=
panel
.
gridPos
.
y
;
for
(
let
index
=
0
;
index
<
selected
.
length
;
index
++
)
{
var
option
=
selected
[
index
];
var
copy
=
this
.
getPanelRepeatClone
(
panel
,
index
,
panelIndex
)
;
for
(
let
index
=
0
;
index
<
selected
Options
.
length
;
index
++
)
{
let
option
=
selectedOptions
[
index
];
let
copy
;
copy
=
this
.
getPanelRepeatClone
(
panel
,
index
,
panelIndex
);
copy
.
scopedVars
=
{};
copy
.
scopedVars
[
variable
.
name
]
=
option
;
if
(
copy
.
type
===
'row'
)
{
// place row below row panels
}
if
(
panel
.
repeatDirection
===
REPEAT_DIR_VERTICAL
)
{
copy
.
gridPos
.
y
=
yPos
;
yPos
+=
copy
.
gridPos
.
h
;
...
...
@@ -334,7 +345,7 @@ export class DashboardModel {
// set width based on how many are selected
// assumed the repeated panels should take up full row width
copy
.
gridPos
.
w
=
Math
.
max
(
GRID_COLUMN_COUNT
/
selected
.
length
,
minWidth
);
copy
.
gridPos
.
w
=
Math
.
max
(
GRID_COLUMN_COUNT
/
selected
Options
.
length
,
minWidth
);
copy
.
gridPos
.
x
=
xPos
;
copy
.
gridPos
.
y
=
yPos
;
...
...
@@ -349,6 +360,90 @@ export class DashboardModel {
}
}
repeatRow
(
panel
:
PanelModel
,
panelIndex
:
number
,
variable
)
{
let
selectedOptions
=
this
.
getSelectedVariableOptions
(
variable
);
let
yPos
=
panel
.
gridPos
.
y
;
function
setScopedVars
(
panel
,
variableOption
)
{
panel
.
scopedVars
=
{};
panel
.
scopedVars
[
variable
.
name
]
=
variableOption
;
}
for
(
let
optionIndex
=
0
;
optionIndex
<
selectedOptions
.
length
;
optionIndex
++
)
{
let
option
=
selectedOptions
[
optionIndex
];
let
rowCopy
=
this
.
getRowRepeatClone
(
panel
,
optionIndex
,
panelIndex
);
setScopedVars
(
rowCopy
,
option
);
let
rowHeight
=
this
.
getRowHeight
(
rowCopy
);
let
rowPanels
=
rowCopy
.
panels
||
[];
let
panelBelowIndex
;
if
(
panel
.
collapsed
)
{
// For collapsed row just copy its panels and set scoped vars and proper IDs
_
.
each
(
rowPanels
,
(
rowPanel
,
i
)
=>
{
setScopedVars
(
rowPanel
,
option
);
if
(
optionIndex
>
0
)
{
this
.
updateRepeatedPanelIds
(
rowPanel
);
}
});
rowCopy
.
gridPos
.
y
+=
optionIndex
;
yPos
+=
optionIndex
;
panelBelowIndex
=
panelIndex
+
optionIndex
+
1
;
}
else
{
// insert after 'row' panel
let
insertPos
=
panelIndex
+
((
rowPanels
.
length
+
1
)
*
optionIndex
)
+
1
;
_
.
each
(
rowPanels
,
(
rowPanel
,
i
)
=>
{
setScopedVars
(
rowPanel
,
option
);
if
(
optionIndex
>
0
)
{
let
cloneRowPanel
=
new
PanelModel
(
rowPanel
);
this
.
updateRepeatedPanelIds
(
cloneRowPanel
);
// For exposed row additionally set proper Y grid position and add it to dashboard panels
cloneRowPanel
.
gridPos
.
y
+=
rowHeight
*
optionIndex
;
this
.
panels
.
splice
(
insertPos
+
i
,
0
,
cloneRowPanel
);
}
});
rowCopy
.
panels
=
[];
rowCopy
.
gridPos
.
y
+=
rowHeight
*
optionIndex
;
yPos
+=
rowHeight
;
panelBelowIndex
=
insertPos
+
rowPanels
.
length
;
}
// Update gridPos for panels below
for
(
let
i
=
panelBelowIndex
;
i
<
this
.
panels
.
length
;
i
++
)
{
this
.
panels
[
i
].
gridPos
.
y
+=
yPos
;
}
}
}
updateRepeatedPanelIds
(
panel
:
PanelModel
)
{
panel
.
repeatPanelId
=
panel
.
id
;
panel
.
id
=
this
.
getNextPanelId
();
panel
.
repeatIteration
=
this
.
iteration
;
panel
.
repeat
=
null
;
return
panel
;
}
getSelectedVariableOptions
(
variable
)
{
let
selectedOptions
;
if
(
variable
.
current
.
text
===
'All'
)
{
selectedOptions
=
variable
.
options
.
slice
(
1
,
variable
.
options
.
length
);
}
else
{
selectedOptions
=
_
.
filter
(
variable
.
options
,
{
selected
:
true
});
}
return
selectedOptions
;
}
getRowHeight
(
rowPanel
:
PanelModel
):
number
{
if
(
!
rowPanel
.
panels
||
rowPanel
.
panels
.
length
===
0
)
{
return
0
;
}
const
positions
=
_
.
map
(
rowPanel
.
panels
,
'gridPos'
);
const
maxPos
=
_
.
maxBy
(
positions
,
(
pos
)
=>
{
return
pos
.
y
+
pos
.
h
;
});
return
maxPos
.
h
+
1
;
}
removePanel
(
panel
:
PanelModel
)
{
var
index
=
_
.
indexOf
(
this
.
panels
,
panel
);
this
.
panels
.
splice
(
index
,
1
);
...
...
public/app/features/dashboard/specs/repeat.jest.ts
View file @
d5023d00
import
_
from
'lodash'
;
import
{
DashboardModel
}
from
'../dashboard_model'
;
import
{
expect
}
from
'test/lib/common'
;
jest
.
mock
(
'app/core/services/context_srv'
,
()
=>
({
...
...
@@ -146,13 +148,13 @@ describe('given dashboard with panel repeat in vertical direction', function() {
});
});
describe
.
skip
(
'given dashboard with row repeat'
,
function
()
{
var
dashboard
;
describe
(
'given dashboard with row repeat'
,
function
()
{
let
dashboard
,
dashboardJSON
;
beforeEach
(
function
()
{
dashboard
=
new
DashboardModel
(
{
dashboard
JSON
=
{
panels
:
[
{
id
:
1
,
type
:
'row'
,
repeat
:
'apps'
,
gridPos
:
{
x
:
0
,
y
:
0
,
h
:
1
,
w
:
24
}
},
{
id
:
1
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
0
,
h
:
1
,
w
:
24
},
repeat
:
'apps'
},
{
id
:
2
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
3
,
type
:
'graph'
,
gridPos
:
{
x
:
6
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
4
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
2
,
h
:
1
,
w
:
24
}},
...
...
@@ -172,33 +174,137 @@ describe.skip('given dashboard with row repeat', function() {
]
}]
}
});
};
dashboard
=
new
DashboardModel
(
dashboardJSON
);
dashboard
.
processRepeats
();
});
it
(
'should not repeat only row'
,
function
()
{
expect
(
dashboard
.
panels
[
1
].
type
).
toBe
(
'graph'
);
});
//
// it('should set scopedVars on panels', function() {
// expect(dashboard.panels[1].scopedVars).toMatchObject({apps: {text: 'se1', value: 'se1'}})
// });
//
// it.skip('should repeat row and panels below two times', function() {
// expect(dashboard.panels).toMatchObject([
// // first (original row)
// {id: 1, type: 'row', repeat: 'apps', gridPos: {x: 0, y: 0, h: 1 , w: 24}},
// {id: 2, type: 'graph', gridPos: {x: 0, y: 1, h: 1 , w: 6}},
// {id: 3, type: 'graph', gridPos: {x: 6, y: 1, h: 1 , w: 6}},
// // repeated row
// {id: 1, type: 'row', repeatPanelId: 1, gridPos: {x: 0, y: 0, h: 1 , w: 24}},
// {id: 2, type: 'graph', repeatPanelId: 1, gridPos: {x: 0, y: 1, h: 1 , w: 6}},
// {id: 3, type: 'graph', repeatPanelId: 1, gridPos: {x: 6, y: 1, h: 1 , w: 6}},
// // row below dont touch
// {id: 4, type: 'row', gridPos: {x: 0, y: 2, h: 1 , w: 24}},
// {id: 5, type: 'graph', gridPos: {x: 0, y: 3, h: 1 , w: 12}},
// ]);
// });
const
panel_types
=
_
.
map
(
dashboard
.
panels
,
'type'
);
expect
(
panel_types
).
toEqual
([
'row'
,
'graph'
,
'graph'
,
'row'
,
'graph'
,
'graph'
,
'row'
,
'graph'
]);
});
it
(
'should set scopedVars for each panel'
,
function
()
{
dashboardJSON
.
templating
.
list
[
0
].
options
[
2
].
selected
=
true
;
dashboard
=
new
DashboardModel
(
dashboardJSON
);
dashboard
.
processRepeats
();
expect
(
dashboard
.
panels
[
1
].
scopedVars
).
toMatchObject
({
apps
:
{
text
:
'se1'
,
value
:
'se1'
}});
expect
(
dashboard
.
panels
[
4
].
scopedVars
).
toMatchObject
({
apps
:
{
text
:
'se2'
,
value
:
'se2'
}});
const
scopedVars
=
_
.
compact
(
_
.
map
(
dashboard
.
panels
,
(
panel
)
=>
{
return
panel
.
scopedVars
?
panel
.
scopedVars
.
apps
.
value
:
null
;
}));
expect
(
scopedVars
).
toEqual
([
'se1'
,
'se1'
,
'se1'
,
'se2'
,
'se2'
,
'se2'
,
'se3'
,
'se3'
,
'se3'
,
]);
});
it
(
'should repeat only configured row'
,
function
()
{
expect
(
dashboard
.
panels
[
6
].
id
).
toBe
(
4
);
expect
(
dashboard
.
panels
[
7
].
id
).
toBe
(
5
);
});
it
(
'should repeat only row if it is collapsed'
,
function
()
{
dashboardJSON
.
panels
=
[
{
id
:
1
,
type
:
'row'
,
collapsed
:
true
,
repeat
:
'apps'
,
gridPos
:
{
x
:
0
,
y
:
0
,
h
:
1
,
w
:
24
},
panels
:
[
{
id
:
2
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
3
,
type
:
'graph'
,
gridPos
:
{
x
:
6
,
y
:
1
,
h
:
1
,
w
:
6
}},
]
},
{
id
:
4
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
24
}},
{
id
:
5
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
2
,
h
:
1
,
w
:
12
}},
];
dashboard
=
new
DashboardModel
(
dashboardJSON
);
dashboard
.
processRepeats
();
const
panel_types
=
_
.
map
(
dashboard
.
panels
,
'type'
);
expect
(
panel_types
).
toEqual
([
'row'
,
'row'
,
'row'
,
'graph'
]);
expect
(
dashboard
.
panels
[
0
].
panels
).
toHaveLength
(
2
);
expect
(
dashboard
.
panels
[
1
].
panels
).
toHaveLength
(
2
);
});
it
(
'should properly repeat multiple rows'
,
function
()
{
dashboardJSON
.
panels
=
[
{
id
:
1
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
0
,
h
:
1
,
w
:
24
},
repeat
:
'apps'
},
// repeat
{
id
:
2
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
3
,
type
:
'graph'
,
gridPos
:
{
x
:
6
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
4
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
2
,
h
:
1
,
w
:
24
}},
// don't touch
{
id
:
5
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
3
,
h
:
1
,
w
:
12
}},
{
id
:
6
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
4
,
h
:
1
,
w
:
24
},
repeat
:
'hosts'
},
// repeat
{
id
:
7
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
5
,
h
:
1
,
w
:
6
}},
{
id
:
8
,
type
:
'graph'
,
gridPos
:
{
x
:
6
,
y
:
5
,
h
:
1
,
w
:
6
}}
];
dashboardJSON
.
templating
.
list
.
push
({
name
:
'hosts'
,
current
:
{
text
:
'backend01, backend02'
,
value
:
[
'backend01'
,
'backend02'
]
},
options
:
[
{
text
:
'backend01'
,
value
:
'backend01'
,
selected
:
true
},
{
text
:
'backend02'
,
value
:
'backend02'
,
selected
:
true
},
{
text
:
'backend03'
,
value
:
'backend03'
,
selected
:
false
}
]
});
dashboard
=
new
DashboardModel
(
dashboardJSON
);
dashboard
.
processRepeats
();
const
panel_types
=
_
.
map
(
dashboard
.
panels
,
'type'
);
expect
(
panel_types
).
toEqual
([
'row'
,
'graph'
,
'graph'
,
'row'
,
'graph'
,
'graph'
,
'row'
,
'graph'
,
'row'
,
'graph'
,
'graph'
,
'row'
,
'graph'
,
'graph'
,
]);
expect
(
dashboard
.
panels
[
0
].
scopedVars
[
'apps'
].
value
).
toBe
(
'se1'
);
expect
(
dashboard
.
panels
[
1
].
scopedVars
[
'apps'
].
value
).
toBe
(
'se1'
);
expect
(
dashboard
.
panels
[
3
].
scopedVars
[
'apps'
].
value
).
toBe
(
'se2'
);
expect
(
dashboard
.
panels
[
4
].
scopedVars
[
'apps'
].
value
).
toBe
(
'se2'
);
expect
(
dashboard
.
panels
[
8
].
scopedVars
[
'hosts'
].
value
).
toBe
(
'backend01'
);
expect
(
dashboard
.
panels
[
9
].
scopedVars
[
'hosts'
].
value
).
toBe
(
'backend01'
);
expect
(
dashboard
.
panels
[
11
].
scopedVars
[
'hosts'
].
value
).
toBe
(
'backend02'
);
expect
(
dashboard
.
panels
[
12
].
scopedVars
[
'hosts'
].
value
).
toBe
(
'backend02'
);
});
it
(
'should assign unique ids for repeated panels'
,
function
()
{
dashboardJSON
.
panels
=
[
{
id
:
1
,
type
:
'row'
,
collapsed
:
true
,
repeat
:
'apps'
,
gridPos
:
{
x
:
0
,
y
:
0
,
h
:
1
,
w
:
24
},
panels
:
[
{
id
:
2
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
6
}},
{
id
:
3
,
type
:
'graph'
,
gridPos
:
{
x
:
6
,
y
:
1
,
h
:
1
,
w
:
6
}},
]
},
{
id
:
4
,
type
:
'row'
,
gridPos
:
{
x
:
0
,
y
:
1
,
h
:
1
,
w
:
24
}},
{
id
:
5
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
2
,
h
:
1
,
w
:
12
}},
];
dashboard
=
new
DashboardModel
(
dashboardJSON
);
dashboard
.
processRepeats
();
const
panel_ids
=
_
.
flattenDeep
(
_
.
map
(
dashboard
.
panels
,
(
panel
)
=>
{
let
ids
=
[];
if
(
panel
.
panels
&&
panel
.
panels
.
length
)
{
ids
=
_
.
map
(
panel
.
panels
,
'id'
);
}
ids
.
push
(
panel
.
id
);
return
ids
;
}));
expect
(
panel_ids
.
length
).
toEqual
(
_
.
uniq
(
panel_ids
).
length
);
});
});
public/sass/components/_sidemenu.scss
View file @
d5023d00
...
...
@@ -4,14 +4,22 @@
flex-flow
:
column
;
flex-direction
:
column
;
width
:
$side-menu-width
;
background
:
$navbarBackground
;
z-index
:
$zindex-sidemenu
;
a
:focus
{
text-decoration
:
none
;
}
.sidemenu__logo_small_breakpoint
{
display
:
none
;
}
.sidemenu__close
{
display
:
none
;
}
}
.sidemenu-open
{
@include
media-breakpoint-up
(
sm
)
{
.sidemenu-open
{
.sidemenu
{
background
:
$side-menu-bg
;
position
:
initial
;
...
...
@@ -24,6 +32,7 @@
.sidemenu__bottom
{
display
:
block
;
}
}
}
.sidemenu__top
{
...
...
@@ -41,6 +50,7 @@
position
:
relative
;
@include
left-brand-border
();
@include
media-breakpoint-up
(
sm
)
{
&
.active
,
&
:hover
{
background-color
:
$side-menu-item-hover-bg
;
...
...
@@ -58,6 +68,7 @@
z-index
:
$zindex-sidemenu
;
}
}
}
}
.dropup.sidemenu-item
:hover
.dropdown-menu
{
...
...
@@ -152,7 +163,7 @@ li.sidemenu-org-switcher {
}
}
.sidemenu__logo
{
.sidemenu__logo
,
.sidemenu__logo_small_breakpoint
{
display
:
block
;
padding
:
0
.4rem
1
.0rem
0
.4rem
0
.65rem
;
min-height
:
$navbarHeight
;
...
...
@@ -170,3 +181,84 @@ li.sidemenu-org-switcher {
}
}
@include
media-breakpoint-down
(
xs
)
{
.sidemenu-open
{
.navbar
{
padding-left
:
60px
!
important
;
}
}
.sidemenu-open--xs
{
.sidemenu
{
width
:
100%
;
background
:
$side-menu-bg
;
position
:
initial
;
height
:
auto
;
box-shadow
:
$side-menu-shadow
;
position
:
relative
;
z-index
:
$zindex-sidemenu
;
}
.sidemenu__close
{
display
:
block
;
font-size
:
$font-size-md
;
}
.sidemenu__top
,
.sidemenu__bottom
{
display
:
block
;
}
}
.sidemenu
{
.sidemenu__logo
{
display
:
none
;
}
.sidemenu__logo_small_breakpoint
{
display
:
flex
;
flex-direction
:
row
;
justify-content
:
space-between
;
align-items
:
baseline
;
&
:hover
{
background
:
transparent
;
}
}
.sidemenu__top
{
padding-top
:
0rem
;
}
.side-menu-header
{
padding-left
:
10px
;
}
.sidemenu-link
{
text-align
:
left
;
}
.sidemenu-icon
{
display
:
none
}
.dropdown-menu--sidemenu
{
display
:
block
;
position
:
unset
;
width
:
100%
;
float
:
none
;
margin-top
:
0
.5rem
;
margin-bottom
:
0
.5rem
;
>
li
>
a
{
padding-left
:
15px
;
}
}
.sidemenu__bottom
{
.dropdown-menu--sidemenu
{
display
:
flex
;
flex-direction
:
column-reverse
;
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment