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
16fbefbb
Commit
16fbefbb
authored
Dec 05, 2017
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into develop-light-theme
parents
df5fd3cd
35f97c9a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
622 additions
and
281 deletions
+622
-281
pkg/api/index.go
+3
-2
public/app/core/angular_wrappers.ts
+2
-0
public/app/core/components/EmptyListCTA/EmptyListCTA.jest.tsx
+22
-0
public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
+34
-0
public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.jest.tsx.snap
+38
-0
public/app/core/nav_model_srv.ts
+0
-8
public/app/core/routes/routes.ts
+5
-0
public/app/features/dashboard/all.ts
+2
-1
public/app/features/dashboard/dashboard_import_ctrl.ts
+6
-19
public/app/features/dashboard/dashboard_list_ctrl.ts
+1
-1
public/app/features/dashboard/dashboard_migration.ts
+3
-2
public/app/features/dashboard/dashboard_model.ts
+133
-38
public/app/features/dashboard/partials/dashboardImport.html
+101
-113
public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts
+25
-28
public/app/features/dashboard/specs/dashboard_migration.jest.ts
+24
-2
public/app/features/dashboard/specs/repeat.jest.ts
+134
-28
public/app/features/dashboard/upload.ts
+1
-1
public/app/features/plugins/partials/ds_list.html
+46
-35
public/sass/_grafana.scss
+1
-0
public/sass/_variables.scss
+5
-2
public/sass/base/_type.scss
+2
-0
public/sass/components/_buttons.scss
+11
-0
public/sass/components/_empty_list_cta.scss
+22
-0
public/sass/components/_tabbed_view.scss
+1
-1
No files found.
pkg/api/index.go
View file @
16fbefbb
...
...
@@ -90,12 +90,13 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
if
c
.
OrgRole
==
m
.
ROLE_ADMIN
||
c
.
OrgRole
==
m
.
ROLE_EDITOR
{
data
.
NavTree
=
append
(
data
.
NavTree
,
&
dtos
.
NavLink
{
Text
:
"Create"
,
Id
:
"create"
,
Icon
:
"fa fa-fw fa-plus"
,
Url
:
"#"
,
Children
:
[]
*
dtos
.
NavLink
{
{
Text
:
"Dashboard"
,
Icon
:
"gicon gicon-dashboard-new"
,
Url
:
setting
.
AppSubUrl
+
"/dashboard/new"
},
{
Text
:
"Folder"
,
Icon
:
"gicon gicon-folder-new"
,
Url
:
setting
.
AppSubUrl
+
"/dashboard/new/?editview=new-folder"
},
{
Text
:
"Import"
,
Icon
:
"gicon gicon-dashboard-import"
,
Url
:
setting
.
AppSubUrl
+
"/dashboard/new/?editview=
import"
},
{
Text
:
"Import"
,
SubTitle
:
"Import dashboard from file or Grafana.com"
,
Id
:
"import"
,
Icon
:
"gicon gicon-dashboard-import"
,
Url
:
setting
.
AppSubUrl
+
"/dashboard/
import"
},
},
})
}
...
...
@@ -103,7 +104,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
dashboardChildNavs
:=
[]
*
dtos
.
NavLink
{
{
Text
:
"Home"
,
Url
:
setting
.
AppSubUrl
+
"/"
,
Icon
:
"fa fa-fw fa-home"
,
HideFromTabs
:
true
},
{
Divider
:
true
,
HideFromTabs
:
true
},
{
Text
:
"Manage"
,
Id
:
"dashboards"
,
Url
:
setting
.
AppSubUrl
+
"/dashboards"
,
Icon
:
"fa fa-fw fa-sitemap"
},
{
Text
:
"Manage"
,
Id
:
"
manage-
dashboards"
,
Url
:
setting
.
AppSubUrl
+
"/dashboards"
,
Icon
:
"fa fa-fw fa-sitemap"
},
{
Text
:
"Playlists"
,
Id
:
"playlists"
,
Url
:
setting
.
AppSubUrl
+
"/playlists"
,
Icon
:
"fa fa-fw fa-film"
},
{
Text
:
"Snapshots"
,
Id
:
"snapshots"
,
Url
:
setting
.
AppSubUrl
+
"/dashboard/snapshots"
,
Icon
:
"icon-gf icon-gf-fw icon-gf-snapshot"
},
}
...
...
public/app/core/angular_wrappers.ts
View file @
16fbefbb
import
{
react2AngularDirective
}
from
'app/core/utils/react2angular'
;
import
{
PasswordStrength
}
from
'./components/PasswordStrength'
;
import
PageHeader
from
'./components/PageHeader'
;
import
EmptyListCTA
from
'./components/EmptyListCTA/EmptyListCTA'
;
export
function
registerAngularDirectives
()
{
react2AngularDirective
(
'passwordStrength'
,
PasswordStrength
,
[
'password'
]);
react2AngularDirective
(
'pageHeader'
,
PageHeader
,
[
'model'
,
"noTabs"
]);
react2AngularDirective
(
'emptyListCta'
,
EmptyListCTA
,
[
'model'
]);
}
public/app/core/components/EmptyListCTA/EmptyListCTA.jest.tsx
0 → 100644
View file @
16fbefbb
import
React
from
'react'
;
import
renderer
from
'react-test-renderer'
;
import
EmptyListCTA
from
'./EmptyListCTA'
;
const
model
=
{
title
:
'Title'
,
buttonIcon
:
'ga css class'
,
buttonLink
:
'http://url/to/destination'
,
buttonTitle
:
'Click me'
,
proTip
:
'This is a tip'
,
proTipLink
:
'http://url/to/tip/destination'
,
proTipLinkTitle
:
'Learn more'
,
proTipTarget
:
'_blank'
};
describe
(
'CollorPalette'
,
()
=>
{
it
(
'renders correctly'
,
()
=>
{
const
tree
=
renderer
.
create
(<
EmptyListCTA
model=
{
model
}
/>).
toJSON
();
expect
(
tree
).
toMatchSnapshot
();
});
});
public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
0 → 100644
View file @
16fbefbb
import
React
,
{
Component
}
from
'react'
;
export
interface
IProps
{
model
:
any
;
}
class
EmptyListCTA
extends
Component
<
IProps
,
any
>
{
render
()
{
const
{
title
,
buttonIcon
,
buttonLink
,
buttonTitle
,
proTip
,
proTipLink
,
proTipLinkTitle
,
proTipTarget
}
=
this
.
props
.
model
;
return
(
<
div
className=
"empty-list-cta p-t-2 p-b-1"
>
<
div
className=
"empty-list-cta__title"
>
{
title
}
</
div
>
<
a
href=
{
buttonLink
}
className=
"empty-list-cta__button btn btn-xlarge btn-success"
><
i
className=
{
buttonIcon
}
/>
{
buttonTitle
}
</
a
>
<
div
className=
"empty-list-cta__pro-tip"
>
<
i
className=
"fa fa-rocket"
/>
ProTip:
{
proTip
}
<
a
className=
"text-link empty-list-cta__pro-tip-link"
href=
{
proTipLink
}
target=
{
proTipTarget
}
>
{
proTipLinkTitle
}
</
a
>
</
div
>
</
div
>
);
}
}
export
default
EmptyListCTA
;
public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.jest.tsx.snap
0 → 100644
View file @
16fbefbb
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollorPalette renders correctly 1`] = `
<div
className="empty-list-cta p-t-2 p-b-1"
>
<div
className="empty-list-cta__title"
>
Title
</div>
<a
className="empty-list-cta__button btn btn-xlarge btn-success"
href="http://url/to/destination"
>
<i
className="ga css class"
/>
Click me
</a>
<div
className="empty-list-cta__pro-tip"
>
<i
className="fa fa-rocket"
/>
ProTip:
This is a tip
<a
className="text-link empty-list-cta__pro-tip-link"
href="http://url/to/tip/destination"
target="_blank"
>
Learn more
</a>
</div>
</div>
`;
public/app/core/nav_model_srv.ts
View file @
16fbefbb
...
...
@@ -119,14 +119,6 @@ export class NavModelSrv {
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'annotations'
)
});
if
(
dashboard
.
meta
.
canAdmin
)
{
menu
.
push
({
title
:
'Permissions...'
,
icon
:
'fa fa-fw fa-lock'
,
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'permissions'
)
});
}
if
(
!
dashboard
.
meta
.
isHome
)
{
menu
.
push
({
title
:
'Version history'
,
...
...
public/app/core/routes/routes.ts
View file @
16fbefbb
...
...
@@ -48,6 +48,11 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
reloadOnSearch
:
false
,
pageClass
:
'page-dashboard'
,
})
.
when
(
'/dashboard/import'
,
{
templateUrl
:
'public/app/features/dashboard/partials/dashboardImport.html'
,
controller
:
'DashboardImportCtrl'
,
controllerAs
:
'ctrl'
,
})
.
when
(
'/datasources'
,
{
templateUrl
:
'public/app/features/plugins/partials/ds_list.html'
,
controller
:
'DataSourcesCtrl'
,
...
...
public/app/features/dashboard/all.ts
View file @
16fbefbb
...
...
@@ -15,7 +15,6 @@ import './unsavedChangesSrv';
import
'./unsaved_changes_modal'
;
import
'./timepicker/timepicker'
;
import
'./upload'
;
import
'./import/dash_import'
;
import
'./export/export_modal'
;
import
'./export_data/export_data_modal'
;
import
'./ad_hoc_filters'
;
...
...
@@ -30,5 +29,7 @@ import './move_to_folder_modal/move_to_folder';
import
coreModule
from
'app/core/core_module'
;
import
{
DashboardListCtrl
}
from
'./dashboard_list_ctrl'
;
import
{
DashboardImportCtrl
}
from
'./dashboard_import_ctrl'
;
coreModule
.
controller
(
'DashboardListCtrl'
,
DashboardListCtrl
);
coreModule
.
controller
(
'DashboardImportCtrl'
,
DashboardImportCtrl
);
public/app/features/dashboard/
import/dash_import
.ts
→
public/app/features/dashboard/
dashboard_import_ctrl
.ts
View file @
16fbefbb
///<reference path="../../../headers/common.d.ts" />
import
coreModule
from
'app/core/core_module'
;
import
config
from
'app/core/config'
;
import
_
from
'lodash'
;
import
config
from
'app/core/config'
;
export
class
DashImportCtrl
{
export
class
DashboardImportCtrl
{
navModel
:
any
;
step
:
number
;
jsonText
:
string
;
parseError
:
string
;
...
...
@@ -17,7 +15,9 @@ export class DashImportCtrl {
gnetInfo
:
any
;
/** @ngInject */
constructor
(
private
backendSrv
,
private
$location
,
private
$scope
,
$routeParams
)
{
constructor
(
private
backendSrv
,
navModelSrv
,
private
$location
,
private
$scope
,
$routeParams
)
{
this
.
navModel
=
navModelSrv
.
getNav
(
'create'
,
'import'
);
this
.
step
=
1
;
this
.
nameExists
=
false
;
...
...
@@ -160,17 +160,4 @@ export class DashImportCtrl {
this
.
gnetError
=
''
;
this
.
gnetInfo
=
''
;
}
}
export
function
dashImportDirective
()
{
return
{
restrict
:
'E'
,
templateUrl
:
'public/app/features/dashboard/import/dash_import.html'
,
controller
:
DashImportCtrl
,
bindToController
:
true
,
controllerAs
:
'ctrl'
,
};
}
coreModule
.
directive
(
'dashImport'
,
dashImportDirective
);
public/app/features/dashboard/dashboard_list_ctrl.ts
View file @
16fbefbb
...
...
@@ -17,7 +17,7 @@ export class DashboardListCtrl {
/** @ngInject */
constructor
(
private
backendSrv
,
navModelSrv
,
private
$q
,
private
searchSrv
:
SearchSrv
)
{
this
.
navModel
=
navModelSrv
.
getNav
(
'dashboards'
,
'dashboards'
,
0
);
this
.
navModel
=
navModelSrv
.
getNav
(
'dashboards'
,
'
manage-
dashboards'
,
0
);
this
.
query
=
{
query
:
''
,
mode
:
'tree'
,
tag
:
[],
starred
:
false
};
this
.
selectedStarredFilter
=
this
.
starredFilterOptions
[
0
];
...
...
public/app/features/dashboard/dashboard_migration.ts
View file @
16fbefbb
...
...
@@ -383,8 +383,8 @@ export class DashboardMigrator {
return
;
}
// Add special "row" panels if even one row is collapsed or has visible title
const
showRows
=
_
.
some
(
old
.
rows
,
(
row
)
=>
row
.
collapse
||
row
.
showTitle
);
// Add special "row" panels if even one row is collapsed
, repeated
or has visible title
const
showRows
=
_
.
some
(
old
.
rows
,
(
row
)
=>
row
.
collapse
||
row
.
showTitle
||
row
.
repeat
);
for
(
let
row
of
old
.
rows
)
{
let
height
:
any
=
row
.
height
||
DEFAULT_ROW_HEIGHT
;
...
...
@@ -398,6 +398,7 @@ export class DashboardMigrator {
rowPanel
.
type
=
'row'
;
rowPanel
.
title
=
row
.
title
;
rowPanel
.
collapsed
=
row
.
collapse
;
rowPanel
.
repeat
=
row
.
repeat
;
rowPanel
.
panels
=
[];
rowPanel
.
gridPos
=
{
x
:
0
,
y
:
yPos
,
w
:
GRID_COLUMN_COUNT
,
h
:
rowGridHeight
};
rowPanelModel
=
new
PanelModel
(
rowPanel
);
...
...
public/app/features/dashboard/dashboard_model.ts
View file @
16fbefbb
...
...
@@ -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
);
}
// 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/
import/dash_i
mport.html
→
public/app/features/dashboard/
partials/dashboardI
mport.html
View file @
16fbefbb
This diff is collapsed.
Click to expand it.
public/app/features/dashboard/specs/dash
_import_ctrl_specs
.ts
→
public/app/features/dashboard/specs/dash
board_import_ctrl.jest
.ts
View file @
16fbefbb
import
{
describe
,
beforeEach
,
it
,
sinon
,
expect
,
angularMocks
}
from
'test/lib/common'
;
import
{
DashboardImportCtrl
}
from
'../dashboard_import_ctrl'
;
import
config
from
'../../../core/config'
;
import
{
DashImportCtrl
}
from
'app/features/dashboard/import/dash_import'
;
import
config
from
'app/core/config'
;
describe
(
'DashImportCtrl'
,
function
()
{
describe
(
'DashboardImportCtrl'
,
function
()
{
var
ctx
:
any
=
{};
var
backendSrv
=
{
search
:
sinon
.
stub
().
returns
(
Promise
.
resolve
([])),
get
:
sinon
.
stub
()
};
beforeEach
(
angularMocks
.
module
(
'grafana.core'
));
let
navModelSrv
;
let
backendSrv
;
beforeEach
(
angularMocks
.
inject
((
$rootScope
,
$controller
,
$q
)
=>
{
ctx
.
$q
=
$q
;
ctx
.
scope
=
$rootScope
.
$new
();
ctx
.
ctrl
=
$controller
(
DashImportCtrl
,
{
$scope
:
ctx
.
scope
,
backendSrv
:
backendSrv
,
});
}));
beforeEach
(()
=>
{
navModelSrv
=
{
getNav
:
()
=>
{}
};
backendSrv
=
{
search
:
jest
.
fn
().
mockReturnValue
(
Promise
.
resolve
([])),
get
:
jest
.
fn
()
};
ctx
.
ctrl
=
new
DashboardImportCtrl
(
backendSrv
,
navModelSrv
,
{},
{},
{});
});
describe
(
'when uploading json'
,
function
()
{
beforeEach
(
function
()
{
...
...
@@ -37,13 +36,13 @@ describe('DashImportCtrl', function() {
});
it
(
'should build input model'
,
function
()
{
expect
(
ctx
.
ctrl
.
inputs
.
length
).
to
.
eql
(
1
);
expect
(
ctx
.
ctrl
.
inputs
[
0
].
name
).
to
.
eql
(
'ds'
);
expect
(
ctx
.
ctrl
.
inputs
[
0
].
info
).
to
.
eql
(
'Select a Test DB data source'
);
expect
(
ctx
.
ctrl
.
inputs
.
length
).
to
Be
(
1
);
expect
(
ctx
.
ctrl
.
inputs
[
0
].
name
).
to
Be
(
'ds'
);
expect
(
ctx
.
ctrl
.
inputs
[
0
].
info
).
to
Be
(
'Select a Test DB data source'
);
});
it
(
'should set inputValid to false'
,
function
()
{
expect
(
ctx
.
ctrl
.
inputsValid
).
to
.
eql
(
false
);
expect
(
ctx
.
ctrl
.
inputsValid
).
to
Be
(
false
);
});
});
...
...
@@ -51,7 +50,7 @@ describe('DashImportCtrl', function() {
beforeEach
(
function
()
{
ctx
.
ctrl
.
gnetUrl
=
'http://grafana.com/dashboards/123'
;
// setup api mock
backendSrv
.
get
=
sinon
.
spy
(()
=>
{
backendSrv
.
get
=
jest
.
fn
(()
=>
{
return
Promise
.
resolve
({
json
:
{}
});
...
...
@@ -60,7 +59,7 @@ describe('DashImportCtrl', function() {
});
it
(
'should call gnet api with correct dashboard id'
,
function
()
{
expect
(
backendSrv
.
get
.
getCall
(
0
).
args
[
0
]).
to
.
eql
(
'api/gnet/dashboards/123'
);
expect
(
backendSrv
.
get
.
mock
.
calls
[
0
][
0
]).
toBe
(
'api/gnet/dashboards/123'
);
});
});
...
...
@@ -68,7 +67,7 @@ describe('DashImportCtrl', function() {
beforeEach
(
function
()
{
ctx
.
ctrl
.
gnetUrl
=
'2342'
;
// setup api mock
backendSrv
.
get
=
sinon
.
spy
(()
=>
{
backendSrv
.
get
=
jest
.
fn
(()
=>
{
return
Promise
.
resolve
({
json
:
{}
});
...
...
@@ -77,10 +76,8 @@ describe('DashImportCtrl', function() {
});
it
(
'should call gnet api with correct dashboard id'
,
function
()
{
expect
(
backendSrv
.
get
.
getCall
(
0
).
args
[
0
]).
to
.
eql
(
'api/gnet/dashboards/2342'
);
expect
(
backendSrv
.
get
.
mock
.
calls
[
0
][
0
]).
toBe
(
'api/gnet/dashboards/2342'
);
});
});
});
public/app/features/dashboard/specs/dashboard_migration.jest.ts
View file @
16fbefbb
...
...
@@ -2,6 +2,7 @@ import _ from 'lodash';
import
{
DashboardModel
}
from
'../dashboard_model'
;
import
{
PanelModel
}
from
'../panel_model'
;
import
{
GRID_CELL_HEIGHT
,
GRID_CELL_VMARGIN
}
from
'app/core/constants'
;
import
{
expect
}
from
'test/lib/common'
;
jest
.
mock
(
'app/core/services/context_srv'
,
()
=>
({}));
...
...
@@ -315,12 +316,33 @@ describe('DashboardModel', function() {
expect
(
panelGridPos
).
toEqual
(
expectedGrid
);
});
it
(
'should add repeated row if repeat set'
,
function
()
{
model
.
rows
=
[
createRow
({
showTitle
:
true
,
title
:
"Row"
,
height
:
8
,
repeat
:
"server"
},
[[
6
]]),
createRow
({
height
:
8
},
[[
12
]])
];
let
dashboard
=
new
DashboardModel
(
model
);
let
panelGridPos
=
getGridPositions
(
dashboard
);
let
expectedGrid
=
[
{
x
:
0
,
y
:
0
,
w
:
24
,
h
:
8
},
{
x
:
0
,
y
:
1
,
w
:
12
,
h
:
8
},
{
x
:
0
,
y
:
9
,
w
:
24
,
h
:
8
},
{
x
:
0
,
y
:
10
,
w
:
24
,
h
:
8
}
];
expect
(
panelGridPos
).
toEqual
(
expectedGrid
);
expect
(
dashboard
.
panels
[
0
].
repeat
).
toBe
(
"server"
);
expect
(
dashboard
.
panels
[
1
].
repeat
).
toBeUndefined
();
expect
(
dashboard
.
panels
[
2
].
repeat
).
toBeUndefined
();
expect
(
dashboard
.
panels
[
3
].
repeat
).
toBeUndefined
();
});
});
});
function
createRow
(
options
,
panelDescriptions
:
any
[])
{
const
PANEL_HEIGHT_STEP
=
GRID_CELL_HEIGHT
+
GRID_CELL_VMARGIN
;
let
{
collapse
,
height
,
showTitle
,
title
}
=
options
;
let
{
collapse
,
height
,
showTitle
,
title
,
repeat
}
=
options
;
height
=
height
*
PANEL_HEIGHT_STEP
;
let
panels
=
[];
_
.
each
(
panelDescriptions
,
panelDesc
=>
{
...
...
@@ -330,7 +352,7 @@ function createRow(options, panelDescriptions: any[]) {
}
panels
.
push
(
panel
);
});
let
row
=
{
collapse
,
height
,
showTitle
,
title
,
panels
};
let
row
=
{
collapse
,
height
,
showTitle
,
title
,
panels
,
repeat
};
return
row
;
}
...
...
public/app/features/dashboard/specs/repeat.jest.ts
View file @
16fbefbb
import
_
from
'lodash'
;
import
{
DashboardModel
}
from
'../dashboard_model'
;
import
{
expect
}
from
'test/lib/common'
;
jest
.
mock
(
'app/core/services/context_srv'
,
()
=>
({
...
...
@@ -146,19 +148,19 @@ 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
}},
{
id
:
5
,
type
:
'graph'
,
gridPos
:
{
x
:
0
,
y
:
3
,
h
:
1
,
w
:
12
}},
],
templating
:
{
templating
:
{
list
:
[{
name
:
'apps'
,
current
:
{
...
...
@@ -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/app/features/dashboard/upload.ts
View file @
16fbefbb
...
...
@@ -4,7 +4,7 @@ import coreModule from 'app/core/core_module';
var
template
=
`
<input type="file" id="dashupload" name="dashupload" class="hide"/>
<label class="btn btn-s
econdary
" for="dashupload">
<label class="btn btn-s
uccess
" for="dashupload">
<i class="fa fa-upload"></i>
Upload .json File
</label>
...
...
public/app/features/plugins/partials/ds_list.html
View file @
16fbefbb
<page-header
model=
"ctrl.navModel"
></page-header>
<div
class=
"page-container page-body"
>
<div
class=
"page-action-bar"
>
<div
class=
"page-action-bar__spacer"
></div>
<a
class=
"page-header__cta btn btn-success"
href=
"datasources/new"
>
<i
class=
"fa fa-plus"
></i>
Add data source
</a>
</div>
<div
ng-if=
"ctrl.datasources.length"
>
<div
class=
"page-action-bar"
>
<div
class=
"page-action-bar__spacer"
></div>
<a
class=
"page-header__cta btn btn-success"
href=
"datasources/new"
>
<i
class=
"fa fa-plus"
></i>
Add data source
</a>
</div>
<section
class=
"card-section"
layout-mode
>
<layout-selector></layout-selector>
<ol
class=
"card-list"
>
<li
class=
"card-item-wrapper"
ng-repeat=
"ds in ctrl.datasources"
>
<a
class=
"card-item"
href=
"datasources/edit/{{ds.id}}/"
>
<div
class=
"card-item-header"
>
<div
class=
"card-item-type"
>
{{ds.type}}
</div>
</div>
<div
class=
"card-item-body"
>
<figure
class=
"card-item-figure"
>
<img
ng-src=
"{{ds.typeLogoUrl}}"
>
</figure>
<div
class=
"card-item-details"
>
<div
class=
"card-item-name"
>
{{ds.name}}
<span
ng-if=
"ds.isDefault"
>
<span
class=
"btn btn-secondary btn-mini"
>
default
</span>
</span>
<section
class=
"card-section"
layout-mode
>
<layout-selector></layout-selector>
<ol
class=
"card-list"
>
<li
class=
"card-item-wrapper"
ng-repeat=
"ds in ctrl.datasources"
>
<a
class=
"card-item"
href=
"datasources/edit/{{ds.id}}/"
>
<div
class=
"card-item-header"
>
<div
class=
"card-item-type"
>
{{ds.type}}
</div>
<div
class=
"card-item-sub-name"
>
{{ds.url}}
</div>
<div
class=
"card-item-body"
>
<figure
class=
"card-item-figure"
>
<img
ng-src=
"{{ds.typeLogoUrl}}"
>
</figure>
<div
class=
"card-item-details"
>
<div
class=
"card-item-name"
>
{{ds.name}}
<span
ng-if=
"ds.isDefault"
>
<span
class=
"btn btn-secondary btn-mini"
>
default
</span>
</span>
</div>
<div
class=
"card-item-sub-name"
>
{{ds.url}}
</div>
</div>
</div>
</
div
>
</
a
>
</
li
>
</
ol
>
</
section
>
</
a
>
</
li
>
</
ol
>
</
section
>
</
div
>
<div
ng-if=
"ctrl.datasources.length === 0"
>
<em>
No data sources defined
</em>
<empty-list-cta
model=
"{
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-dashboard-new',
buttonLink: '/datasources/new',
buttonTitle: 'Add data source',
proTip: 'You can also define data sources through configuration files.',
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',
proTipLinkTitle: 'Learn more',
proTipTarget: '_blank'
}"
/>
</div>
</div>
public/sass/_grafana.scss
View file @
16fbefbb
...
...
@@ -86,6 +86,7 @@
@import
"components/dashboard_grid"
;
@import
"components/dashboard_list"
;
@import
"components/page_header"
;
@import
"components/empty_list_cta"
;
// PAGES
...
...
public/sass/_variables.scss
View file @
16fbefbb
...
...
@@ -218,8 +218,11 @@ $btn-font-weight: 500 !default;
$btn-padding-x-sm
:
.5rem
!
default
;
$btn-padding-y-sm
:
.25rem
!
default
;
$btn-padding-x-lg
:
1
.5rem
!
default
;
$btn-padding-y-lg
:
.75rem
!
default
;
$btn-padding-x-lg
:
21px
!
default
;
$btn-padding-y-lg
:
11px
!
default
;
$btn-padding-x-xl
:
21px
!
default
;
$btn-padding-y-xl
:
11px
!
default
;
$btn-border-radius
:
3px
;
...
...
public/sass/base/_type.scss
View file @
16fbefbb
...
...
@@ -48,6 +48,8 @@ a.text-success:hover,
a
.text-success
:focus
{
color
:
darken
(
$success-text-color
,
10%
);
}
a
{
cursor
:
pointer
;
}
.text-link
{
text-decoration
:
underline
;
}
a
:focus
{
outline
:
0
none
!
important
;
}
...
...
public/sass/components/_buttons.scss
View file @
16fbefbb
...
...
@@ -51,10 +51,21 @@
// Button Sizes
// --------------------------------------------------
// XLarge
.btn-xlarge
{
@include
button-size
(
$btn-padding-y-xl
,
$btn-padding-x-xl
,
$font-size-lg
,
$btn-border-radius
);
font-weight
:
normal
;
padding-bottom
:
$btn-padding-y-xl
-
3
;
.gicon
{
font-size
:
31px
;
margin-right
:
1rem
;
}
}
// Large
.btn-large
{
@include
button-size
(
$btn-padding-y-lg
,
$btn-padding-x-lg
,
$font-size-lg
,
$btn-border-radius
);
font-weight
:
normal
;
}
.btn-small
{
...
...
public/sass/components/_empty_list_cta.scss
0 → 100644
View file @
16fbefbb
.empty-list-cta
{
background-color
:
$search-filter-box-bg
;
text-align
:
center
;
}
.empty-list-cta__title
{
padding-bottom
:
30px
;
font-style
:
italic
;
}
.empty-list-cta__button
{
margin-bottom
:
50px
;
}
.empty-list-cta__pro-tip
{
padding-bottom
:
20px
;
}
.empty-list-cta__pro-tip-link
{
margin-left
:
5px
;
}
\ No newline at end of file
public/sass/components/_tabbed_view.scss
View file @
16fbefbb
...
...
@@ -26,7 +26,7 @@
.tabbed-view-panel-title
{
float
:
left
;
padding-top
:
1rem
;
padding-top
:
9px
;
margin
:
0
2rem
0
0
;
}
...
...
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