Commit 60e7c6d1 by Torkel Ödegaard

Merge branch 'master' into apps

Conflicts:
	public/app/partials/sidemenu.html
parents f67563e9 a9aaa5ad
...@@ -44,7 +44,7 @@ docs-test: docs-build ...@@ -44,7 +44,7 @@ docs-test: docs-build
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh
docs-build: docs-build:
git fetch https://github.com/grafana/grafana.git docs-2.1 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files git fetch https://github.com/grafana/grafana.git docs-2.5 && git diff --name-status FETCH_HEAD...HEAD -- . > changed-files
echo "$(GIT_BRANCH)" > GIT_BRANCH echo "$(GIT_BRANCH)" > GIT_BRANCH
echo "$(GITCOMMIT)" > GITCOMMIT echo "$(GITCOMMIT)" > GITCOMMIT
docker build -t "$(DOCKER_DOCS_IMAGE)" . docker build -t "$(DOCKER_DOCS_IMAGE)" .
...@@ -62,6 +62,7 @@ pages: ...@@ -62,6 +62,7 @@ pages:
- ['reference/templating.md', 'Reference', 'Templating'] - ['reference/templating.md', 'Reference', 'Templating']
- ['reference/scripting.md', 'Reference', 'Scripting'] - ['reference/scripting.md', 'Reference', 'Scripting']
- ['reference/playlist.md', 'Reference', 'Playlist'] - ['reference/playlist.md', 'Reference', 'Playlist']
- ['reference/plugins.md', 'Reference', 'Plugins']
- ['reference/export_import.md', 'Reference', 'Import & Export'] - ['reference/export_import.md', 'Reference', 'Import & Export']
- ['reference/admin.md', 'Reference', 'Administration'] - ['reference/admin.md', 'Reference', 'Administration']
- ['reference/http_api.md', 'Reference', 'HTTP API'] - ['reference/http_api.md', 'Reference', 'HTTP API']
......
...@@ -11,7 +11,7 @@ dev environment. Grafana ships with its own required backend server; also comple ...@@ -11,7 +11,7 @@ dev environment. Grafana ships with its own required backend server; also comple
## Dependencies ## Dependencies
- [Go 1.4](https://golang.org/dl/) - [Go 1.5](https://golang.org/dl/)
- [NodeJS](https://nodejs.org/download/) - [NodeJS](https://nodejs.org/download/)
## Get Code ## Get Code
......
---
page_title: Plugin guide
page_description: Plugin guide for Grafana
page_keywords: grafana, plugins, documentation
---
# Plugins
!Plugin support for panels is only available in nightly!
Adding support for all datasources and suggested panels would bloat grafana and make it impossible to maintain. That's why we implemented a plugin system that makes it possible for anyone to develop support for a datasource or custom panel without adding it to Grafana itself.
## Installing plugins
Installing a plugin is very simple. Just download it and place it in the Grafana plugins folder and restart grafana.
The default plugin folder is configurable under paths.plugins
It's also possible to add one specific plugin by linking to its folder.
```
[plugin.mirror]
path = /home/evil-queen/datasource-plugin-mirror
```
## Plugin implementation ##
Each plugin is defined in plugin.json file in the plugin folder.
Instead of massive documentation about how it works we created a reference implementation of a plugin.
You can find each reference implementation further down on this page.
## Datasource plugins
Datasource have three responsibilities.
* UI for configuring its settings
* Datasource object that can send queries, metricqueries and healthcheck the datasource
* Query editor within panels
https://github.com/grafana/datasource-plugin-genericdatasource
## Panel plugins
Panel plugins are responsible for
* UI for Panel options.
* Creating a directive that can render something based on datasource data.
We currently dont have a reference implementation for panel plugins but you can checkout https://github.com/grafana/panel-plugin-piechart
...@@ -43,18 +43,29 @@ func init() { ...@@ -43,18 +43,29 @@ func init() {
} }
} }
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { var awsCredentials map[string]*credentials.Credentials = make(map[string]*credentials.Credentials)
func getCredentials(profile string) *credentials.Credentials {
if _, ok := awsCredentials[profile]; ok {
return awsCredentials[profile]
}
sess := session.New() sess := session.New()
creds := credentials.NewChainCredentials( creds := credentials.NewChainCredentials(
[]credentials.Provider{ []credentials.Provider{
&credentials.EnvProvider{}, &credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, &credentials.SharedCredentialsProvider{Filename: "", Profile: profile},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
}) })
awsCredentials[profile] = creds
return creds
}
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: creds, Credentials: getCredentials(req.DataSource.Database),
} }
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
...@@ -92,17 +103,9 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { ...@@ -92,17 +103,9 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
} }
func handleListMetrics(req *cwRequest, c *middleware.Context) { func handleListMetrics(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: creds, Credentials: getCredentials(req.DataSource.Database),
} }
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
...@@ -140,17 +143,9 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { ...@@ -140,17 +143,9 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: creds, Credentials: getCredentials(req.DataSource.Database),
} }
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
...@@ -188,17 +183,9 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { ...@@ -188,17 +183,9 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: creds, Credentials: getCredentials(req.DataSource.Database),
} }
svc := cloudwatch.New(session.New(cfg), cfg) svc := cloudwatch.New(session.New(cfg), cfg)
...@@ -232,17 +219,9 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) { ...@@ -232,17 +219,9 @@ func handleDescribeAlarmHistory(req *cwRequest, c *middleware.Context) {
} }
func handleDescribeInstances(req *cwRequest, c *middleware.Context) { func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
sess := session.New()
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
cfg := &aws.Config{ cfg := &aws.Config{
Region: aws.String(req.Region), Region: aws.String(req.Region),
Credentials: creds, Credentials: getCredentials(req.DataSource.Database),
} }
svc := ec2.New(session.New(cfg), cfg) svc := ec2.New(session.New(cfg), cfg)
......
...@@ -24,26 +24,32 @@ function (angular, _, $, coreModule, config) { ...@@ -24,26 +24,32 @@ function (angular, _, $, coreModule, config) {
}); });
}; };
$scope.loadOrgs = function() { $scope.openUserDropdown = function() {
$scope.orgMenu = []; $scope.orgMenu = [
{section: 'You', cssClass: 'dropdown-menu-title'},
{text: 'Profile', url: $scope.getUrl('/profile')},
];
if (contextSrv.hasRole('Admin')) { if (contextSrv.hasRole('Admin')) {
$scope.orgMenu.push({section: contextSrv.user.orgName, cssClass: 'dropdown-menu-title'});
$scope.orgMenu.push({ $scope.orgMenu.push({
text: "Organization settings", text: "Settings",
href: $scope.getUrl("/org"), url: $scope.getUrl("/org"),
}); });
$scope.orgMenu.push({ $scope.orgMenu.push({
text: "Users", text: "Users",
href: $scope.getUrl("/org/users"), url: $scope.getUrl("/org/users"),
}); });
$scope.orgMenu.push({ $scope.orgMenu.push({
text: "API Keys", text: "API Keys",
href: $scope.getUrl("/org/apikeys"), url: $scope.getUrl("/org/apikeys"),
}); });
} }
if ($scope.orgMenu.length > 0) { $scope.orgMenu.push({cssClass: "divider"});
$scope.orgMenu.push({ cssClass: 'divider' });
if (config.allowOrgCreate) {
$scope.orgMenu.push({text: "New organization", icon: "fa fa-fw fa-plus", url: $scope.getUrl('/org/new')});
} }
backendSrv.get('/api/user/orgs').then(function(orgs) { backendSrv.get('/api/user/orgs').then(function(orgs) {
...@@ -61,12 +67,12 @@ function (angular, _, $, coreModule, config) { ...@@ -61,12 +67,12 @@ function (angular, _, $, coreModule, config) {
}); });
}); });
if (config.allowOrgCreate) { $scope.orgMenu.push({cssClass: "divider"});
$scope.orgMenu.push({ if (contextSrv.isGrafanaAdmin) {
text: "New Organization", $scope.orgMenu.push({text: "Server admin", url: $scope.getUrl("/admin/settings")});
icon: "fa fa-fw fa-plus", }
href: $scope.getUrl('/org/new') if (contextSrv.isSignedIn) {
}); $scope.orgMenu.push({text: "Sign out", url: $scope.getUrl("/logout"), target: "_self"});
} }
}); });
}; };
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<table class="grafana-options-table"> <table class="grafana-options-table">
<tr ng-repeat="annotation in annotations"> <tr ng-repeat="annotation in annotations">
<td style="width:90%"> <td style="width:90%">
<i class="fa fa-bolt"></i> &nbsp; <i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp;
{{annotation.name}} {{annotation.name}}
</td> </td>
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td> <td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
......
...@@ -6,8 +6,4 @@ define([ ...@@ -6,8 +6,4 @@ define([
'./userInviteCtrl', './userInviteCtrl',
'./orgApiKeysCtrl', './orgApiKeysCtrl',
'./orgDetailsCtrl', './orgDetailsCtrl',
'./app_list_ctrl',
'./app_edit_ctrl',
'./app_srv',
'./app_directive',
], function () {}); ], function () {});
...@@ -8,41 +8,21 @@ ...@@ -8,41 +8,21 @@
</a> </a>
</li> </li>
<li class="sidemenu-system-section" ng-if="systemSection"> <li class="sidemenu-org-section dropdown" ng-if="contextSrv.isSignedIn">
<div class="sidemenu-system-section-inner"> <div class="sidemenu-org" data-toggle="dropdown" ng-click="openUserDropdown()">
<i class="fa fa-fw fa-cubes"></i> <div class="sidemenu-org-avatar">
<div class="sidemenu-section-text-wrapper"> <img ng-src="{{contextSrv.user.gravatarUrl}}">
<div class="sidemenu-section-heading">Grafana Admin</div>
<div class="sidemenu-section-tagline">v {{grafanaVersion}}</div>
</div> </div>
<div class="sidemenu-org-details">
<span class="sidemenu-org-user sidemenu-item-text">{{contextSrv.user.name}}</span>
<span class="sidemenu-org-name sidemenu-item-text">{{contextSrv.user.orgName}}</span>
</div> </div>
</li> <i class="fa fa-caret-down small"></i>
</div>
<li ng-repeat="item in mainLinks"> <ul class="dropdown-menu" role="menu">
<a href="{{item.url}}" class="sidemenu-item" target="{{item.target}}">
<span class="icon-circle sidemenu-icon"><i class="{{item.icon}}"></i></span>
<span class="sidemenu-item-text">{{item.text}}</span>
</a>
</li>
</ul>
<ul class="sidemenu sidemenu-small" style="margin-top:50px" ng-if="!systemSection">
<li ng-if="contextSrv.user.isSignedIn">
<a href="profile" class="sidemenu-item">
<img ng-src="{{contextSrv.user.gravatarUrl}}">
<span class="sidemenu-item-text">{{contextSrv.user.name}}</span>
</a>
</li>
<li class="dropdown">
<a class="sidemenu-item pointer" data-toggle="dropdown" ng-click="loadOrgs()" tabindex="0">
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-users"></i></span>
<span class="sidemenu-item-text">{{contextSrv.user.orgName}}</span><i class="fa fa-caret-down small"></i>
</a>
<ul class="dropdown-menu" role="menu" style="left: 65px">
<li ng-repeat="menuItem in orgMenu" ng-class="menuItem.cssClass"> <li ng-repeat="menuItem in orgMenu" ng-class="menuItem.cssClass">
<a href="{{menuItem.href}}" ng-if="menuItem.href"> <span ng-if="menuItem.section">{{menuItem.section}}</span>
<a href="{{menuItem.url}}" ng-if="menuItem.url" target="{{menuItem.target}}">
<i class="{{menuItem.icon}}" ng-if="menuItem.icon"></i> <i class="{{menuItem.icon}}" ng-if="menuItem.icon"></i>
{{menuItem.text}} {{menuItem.text}}
</a> </a>
...@@ -54,26 +34,22 @@ ...@@ -54,26 +34,22 @@
</ul> </ul>
</li> </li>
<li ng-if="contextSrv.isGrafanaAdmin"> <li class="sidemenu-system-section" ng-if="systemSection">
<a href="admin/settings" class="sidemenu-item"> <div class="sidemenu-system-section-inner">
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-cog"></i></span> <i class="fa fa-fw fa-cubes"></i>
<span class="sidemenu-item-text">Grafana admin</span> <div class="sidemenu-section-text-wrapper">
</a> <div class="sidemenu-section-heading">Grafana Admin</div>
</li> <div class="sidemenu-section-tagline">v {{grafanaVersion}}</div>
<li ng-if="showSignout"> </div>
<a href="logout" class="sidemenu-item" target="_self"> </div>
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-out"></i></span>
<span class="sidemenu-item-text">Sign out</span>
</a>
</li> </li>
<li ng-if="!contextSrv.isSignedIn"> <li ng-repeat="item in mainLinks">
<a href="login" class="sidemenu-item" target="_self"> <a href="{{item.url}}" class="sidemenu-item sidemenu-main-link" target="{{item.target}}">
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-in"></i></span> <span class="icon-circle sidemenu-icon"><i class="{{item.icon}}"></i></span>
<span class="sidemenu-item-text">Sign in</span> <span class="sidemenu-item-text">{{item.text}}</span>
</a> </a>
</li> </li>
</ul>
<ul class="sidemenu sidemenu-small" style="margin-top:50px" ng-if="systemSection"> <ul class="sidemenu sidemenu-small" style="margin-top:50px" ng-if="systemSection">
<li> <li>
...@@ -82,12 +58,7 @@ ...@@ -82,12 +58,7 @@
<span class="sidemenu-item-text">Exit admin</span> <span class="sidemenu-item-text">Exit admin</span>
</a> </a>
</li> </li>
<li ng-if="showSignout"> </ul>
<a href="logout" class="sidemenu-item" target="_self">
<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-out"></i></span>
<span class="sidemenu-item-text">Sign out</span>
</a>
</li>
</ul> </ul>
</div> </div>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<ul class="tight-form-list" ng-if="dashboard.annotations.list.length > 0"> <ul class="tight-form-list" ng-if="dashboard.annotations.list.length > 0">
<li ng-repeat="annotation in dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}"> <li ng-repeat="annotation in dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
<a ng-click="disableAnnotation(annotation)"> <a ng-click="disableAnnotation(annotation)">
<i class="fa fa-bolt"></i> <i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
{{annotation.name}} {{annotation.name}}
<input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable"> <input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable">
<label for="hideYAxis" class="cr1"></label> <label for="hideYAxis" class="cr1"></label>
......
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
} }
.sidemenu { .sidemenu {
font-size: 16px;
font-weight: @baseFontWeight; font-weight: @baseFontWeight;
list-style: none; list-style: none;
margin: 0; margin: 0;
...@@ -48,32 +47,11 @@ ...@@ -48,32 +47,11 @@
top: 2px; top: 2px;
font-size: 90%; font-size: 90%;
} }
&.sidemenu-small {
font-size: 14px;
.icon-circle {
border-radius: 50%;
background: @iconContainerBackground;
box-shadow: @iconContainerShadow;
border: @iconContainerBorder;
width: 28px;
height: 28px;
i {
top: 1px;
left: 4px;
font-size: 110%;
}
}
.sidemenu-item {
// color: @textColor;
line-height: 28px;
padding-left: 25px;
}
}
} }
.sidemenu-main-link {
font-size: 16px;
}
.sidemenu-item-text { .sidemenu-item-text {
width: 110px; width: 110px;
...@@ -162,6 +140,7 @@ ...@@ -162,6 +140,7 @@
padding: 0 15px; padding: 0 15px;
} }
} }
.sidemenu-section-tagline { .sidemenu-section-tagline {
font-style: italic; font-style: italic;
font-size: 75%; font-size: 75%;
...@@ -171,3 +150,80 @@ ...@@ -171,3 +150,80 @@
.sidemenu-section-text-wrapper { .sidemenu-section-text-wrapper {
padding-top: 4px; padding-top: 4px;
} }
.sidemenu-org-section .dropdown-menu {
top: 51%;
left: 100px;
}
.sidemenu-org-section .dropdown-menu-title {
margin: 0 10px 0 6px;
padding: 10px 0 0;
overflow: hidden;
color: @dropdownTitle;
font-weight: bold;
}
.sidemenu-org-section .dropdown-menu-title > span {
display: inline-block;
position: relative;
&:after {
display: block;
position: absolute;
top: 50%;
right: 0;
left: 100%;
width: 200px;
height: 1px;
margin-left: 5px;
background: @dropdownDivider;
content: '';
}
}
.sidemenu-org {
display: table;
position: relative;
width: 159px;
padding: 2px 10px 20px 21px;
border-bottom: 1px solid @sideMenuOrgBorder;
cursor: pointer;
}
.sidemenu-org .fa-caret-down {
position: absolute;
top: 33px;
right: 2px;
}
.sidemenu-org-avatar,
.sidemenu-org-details {
display: table-cell;
vertical-align: top;
}
.sidemenu-org-avatar {
width: 44px;
}
.sidemenu-org-avatar > img {
width: 44px;
height: 44px;
border-radius: 50%;
}
.sidemenu-org-details {
padding-left: 12px;
color: @linkColor;
}
.sidemenu-org-user,
.sidemenu-org-name {
display: block;
}
.sidemenu-org-user {
font-size: 14px;
}
...@@ -157,6 +157,9 @@ ...@@ -157,6 +157,9 @@
@formActionsBackground: transparent; @formActionsBackground: transparent;
@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border @inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border
// Sidemenu
// -------------------------
@sideMenuOrgBorder: rgb(37,37,37);
// Dropdowns // Dropdowns
// ------------------------- // -------------------------
...@@ -164,6 +167,8 @@ ...@@ -164,6 +167,8 @@
@dropdownBorder: rgba(0,0,0,.2); @dropdownBorder: rgba(0,0,0,.2);
@dropdownDividerTop: transparent; @dropdownDividerTop: transparent;
@dropdownDividerBottom: #444; @dropdownDividerBottom: #444;
@dropdownDivider: @dropdownDividerBottom;
@dropdownTitle: @white;
@dropdownLinkColor: @textColor; @dropdownLinkColor: @textColor;
@dropdownLinkColorHover: @white; @dropdownLinkColorHover: @white;
......
...@@ -171,12 +171,18 @@ ...@@ -171,12 +171,18 @@
@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border @inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border
@inputText: #020202; @inputText: #020202;
// Sidemenu
// -------------------------
@sideMenuOrgBorder: #555;
// Dropdowns // Dropdowns
// ------------------------- // -------------------------
@dropdownBackground: @white; @dropdownBackground: @white;
@dropdownBorder: rgba(0,0,0,.2); @dropdownBorder: rgba(0,0,0,.2);
@dropdownDividerTop: #e5e5e5; @dropdownDividerTop: #e5e5e5;
@dropdownDividerBottom: @white; @dropdownDividerBottom: @white;
@dropdownDivider: @dropdownDividerTop;
@dropdownTitle: #333;
@dropdownLinkColor: @grayDark; @dropdownLinkColor: @grayDark;
@dropdownLinkColorHover: @white; @dropdownLinkColorHover: @white;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment