Commit d630f3d5 by Torkel Ödegaard

Merge branch 'external-user-mng' into v4.4.x

parents 7b768bca 06e87c15
...@@ -204,6 +204,11 @@ login_hint = email or username ...@@ -204,6 +204,11 @@ login_hint = email or username
# Default UI theme ("dark" or "light") # Default UI theme ("dark" or "light")
default_theme = dark default_theme = dark
# External user management
external_manage_link_url =
external_manage_link_name =
external_manage_info =
[auth] [auth]
# Set to true to disable (hide) the login form, useful if you use OAuth # Set to true to disable (hide) the login form, useful if you use OAuth
disable_login_form = false disable_login_form = false
......
...@@ -191,6 +191,11 @@ ...@@ -191,6 +191,11 @@
# Default UI theme ("dark" or "light") # Default UI theme ("dark" or "light")
;default_theme = dark ;default_theme = dark
# External user management, these options affect the organization users view
;external_manage_link_url =
;external_manage_link_name =
;external_manage_info =
[auth] [auth]
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false # Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false ;disable_login_form = false
......
...@@ -131,17 +131,20 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro ...@@ -131,17 +131,20 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
} }
jsonObj := map[string]interface{}{ jsonObj := map[string]interface{}{
"defaultDatasource": defaultDatasource, "defaultDatasource": defaultDatasource,
"datasources": datasources, "datasources": datasources,
"panels": panels, "panels": panels,
"appSubUrl": setting.AppSubUrl, "appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin, "allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled, "authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled, "ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled, "alertingEnabled": setting.AlertingEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId, "googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm, "disableLoginForm": setting.DisableLoginForm,
"disableSignoutMenu": setting.DisableSignoutMenu, "disableSignoutMenu": setting.DisableSignoutMenu,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"buildInfo": map[string]interface{}{ "buildInfo": map[string]interface{}{
"version": setting.BuildVersion, "version": setting.BuildVersion,
"commit": setting.BuildCommit, "commit": setting.BuildCommit,
......
...@@ -90,15 +90,18 @@ var ( ...@@ -90,15 +90,18 @@ var (
SnapShotRemoveExpired bool SnapShotRemoveExpired bool
// User settings // User settings
AllowUserSignUp bool AllowUserSignUp bool
AllowUserOrgCreate bool AllowUserOrgCreate bool
AutoAssignOrg bool AutoAssignOrg bool
AutoAssignOrgRole string AutoAssignOrgRole string
VerifyEmailEnabled bool VerifyEmailEnabled bool
LoginHint string LoginHint string
DefaultTheme string DefaultTheme string
DisableLoginForm bool DisableLoginForm bool
DisableSignoutMenu bool DisableSignoutMenu bool
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
// Http auth // Http auth
AdminUser string AdminUser string
...@@ -531,6 +534,9 @@ func NewConfigContext(args *CommandLineArgs) error { ...@@ -531,6 +534,9 @@ func NewConfigContext(args *CommandLineArgs) error {
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String() LoginHint = users.Key("login_hint").String()
DefaultTheme = users.Key("default_theme").String() DefaultTheme = users.Key("default_theme").String()
ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
ExternalUserMngLinkName = users.Key("external_manage_link_name").String()
ExternalUserMngInfo = users.Key("external_manage_info").String()
// auth // auth
auth := Cfg.Section("auth") auth := Cfg.Section("auth")
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import config from 'app/core/config'; import config from 'app/core/config';
import _ from 'lodash'; import _ from 'lodash';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import Remarkable from 'remarkable';
export class OrgUsersCtrl { export class OrgUsersCtrl {
...@@ -10,11 +11,14 @@ export class OrgUsersCtrl { ...@@ -10,11 +11,14 @@ export class OrgUsersCtrl {
users: any; users: any;
pendingInvites: any; pendingInvites: any;
editor: any; editor: any;
showInviteUI: boolean;
navModel: any; navModel: any;
externalUserMngLinkUrl: string;
externalUserMngLinkName: string;
externalUserMngInfo: string;
addUsersBtnName: string;
/** @ngInject */ /** @ngInject */
constructor(private $scope, private $http, private backendSrv, navModelSrv) { constructor(private $scope, private $http, private backendSrv, navModelSrv, $sce) {
this.user = { this.user = {
loginOrEmail: '', loginOrEmail: '',
role: 'Viewer', role: 'Viewer',
...@@ -23,7 +27,27 @@ export class OrgUsersCtrl { ...@@ -23,7 +27,27 @@ export class OrgUsersCtrl {
this.get(); this.get();
this.editor = { index: 0 }; this.editor = { index: 0 };
this.showInviteUI = config.disableLoginForm === false; this.externalUserMngLinkUrl = config.externalUserMngLinkUrl;
this.externalUserMngLinkName = config.externalUserMngLinkName;
// render external user management info markdown
if (config.externalUserMngInfo) {
this.externalUserMngInfo = new Remarkable({
linkTarget: '__blank',
}).render(config.externalUserMngInfo);
}
this.addUsersBtnName = this.getAddUserBtnName();
}
getAddUserBtnName(): string {
if (this.externalUserMngLinkName) {
return this.externalUserMngLinkName;
} else if (config.disableLoginForm) {
return "Add Users";
} else {
return "Add or Invite";
}
} }
get() { get() {
...@@ -68,13 +92,13 @@ export class OrgUsersCtrl { ...@@ -68,13 +92,13 @@ export class OrgUsersCtrl {
evt.stopPropagation(); evt.stopPropagation();
} }
openInviteModal() { openAddUsersView() {
var modalScope = this.$scope.$new(); var modalScope = this.$scope.$new();
modalScope.invitesSent = this.get.bind(this); modalScope.invitesSent = this.get.bind(this);
var src = this.showInviteUI var src = config.disableLoginForm
? 'public/app/features/org/partials/invite.html' ? 'public/app/features/org/partials/add_user.html'
: 'public/app/features/org/partials/add_user.html'; : 'public/app/features/org/partials/invite.html';
this.$scope.appEvent('show-modal', { this.$scope.appEvent('show-modal', {
src: src, src: src,
......
...@@ -5,11 +5,17 @@ ...@@ -5,11 +5,17 @@
<h1>Organization users</h1> <h1>Organization users</h1>
<div class="page-header-tabs"> <div class="page-header-tabs">
<button class="btn btn-success" ng-click="ctrl.openInviteModal()">
<button class="btn btn-success" ng-click="ctrl.openAddUsersView()" ng-hide="ctrl.externalUserMngLinkUrl">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
Add <span ng-show="ctrl.showInviteUI"> or Invite</span> <span>{{ctrl.addUsersBtnName}}</span>
</button> </button>
<a class="btn btn-inverse" ng-href="{{ctrl.externalUserMngLinkUrl}}" target="_blank" ng-if="ctrl.externalUserMngLinkUrl">
<i class="fa fa-external-link-square"></i>
{{ctrl.addUsersBtnName}}
</a>
<ul class="gf-tabs"> <ul class="gf-tabs">
<li class="gf-tabs-item"> <li class="gf-tabs-item">
<a class="gf-tabs-link" ng-click="ctrl.editor.index = 0" ng-class="{active: ctrl.editor.index === 0}"> <a class="gf-tabs-link" ng-click="ctrl.editor.index = 0" ng-class="{active: ctrl.editor.index === 0}">
...@@ -25,72 +31,76 @@ ...@@ -25,72 +31,76 @@
</div> </div>
</div> </div>
<div ng-if="ctrl.editor.index === 0" class="tab-content"> <div class="grafana-info-box" ng-if="ctrl.externalUserMngInfo">
<table class="filter-table form-inline"> <span ng-bind-html="ctrl.externalUserMngInfo"></span>
<thead> </div>
<tr>
<th>Login</th>
<th>Email</th>
<th>Role</th>
<th style="width: 34px;"></th>
</tr>
</thead>
<tr ng-repeat="user in ctrl.users">
<td>{{user.login}}</td>
<td><span class="ellipsis">{{user.email}}</span></td>
<td>
<select type="text" ng-model="user.role" class="input-medium" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
</select>
</td>
<td>
<a ng-click="ctrl.removeUser(user)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</div>
<div ng-if="ctrl.editor.index === 1 && ctrl.showInviteUI"> <div ng-if="ctrl.editor.index === 0" class="tab-content">
<table class="filter-table form-inline"> <table class="filter-table form-inline">
<thead> <thead>
<tr> <tr>
<th>Email</th> <th>Login</th>
<th>Name</th> <th>Email</th>
<th></th> <th>Role</th>
</tr> <th style="width: 34px;"></th>
</thead> </tr>
<tbody ng-repeat="invite in ctrl.pendingInvites"> </thead>
<tr ng-click="invite.expanded = !invite.expanded" ng-class="{'expanded': invite.expanded}"> <tr ng-repeat="user in ctrl.users">
<td>{{invite.email}}</td> <td>{{user.login}}</td>
<td>{{invite.name}}</td> <td><span class="ellipsis">{{user.email}}</span></td>
<td class="text-right"> <td>
<button class="btn btn-inverse btn-mini " data-clipboard-text="{{invite.url}}" clipboard-button ng-click="ctrl.copyInviteToClipboard($event)"> <select type="text" ng-model="user.role" class="input-medium" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
<i class="fa fa-clipboard"></i> Copy Invite </select>
</button> </td>
&nbsp; <td>
<button class="btn btn-inverse btn-mini"> <a ng-click="ctrl.removeUser(user)" class="btn btn-danger btn-mini">
Details <i class="fa fa-remove"></i>
<i ng-show="!invite.expanded" class="fa fa-caret-right"></i> </a>
<i ng-show="invite.expanded" class="fa fa-caret-down"></i> </td>
</button> </tr>
</td> </table>
</tr> </div>
<tr ng-show="invite.expanded">
<td colspan="3">
<a href="{{invite.url}}">{{invite.url}}</a><br><br>
&nbsp;
<button class="btn btn-inverse btn-mini" ng-click="ctrl.revokeInvite(invite, $event)">
<i class="fa fa-remove" style="color: red"></i> Revoke invite
</button>
<span style="padding-left: 15px">
Invited: <em> {{invite.createdOn | date: 'shortDate'}} by {{invite.invitedBy}} </em>
</span>
</td>
</tr>
</tbody>
</table>
</div> <div ng-if="ctrl.editor.index === 1 && ctrl.showInviteUI">
<table class="filter-table form-inline">
<thead>
<tr>
<th>Email</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody ng-repeat="invite in ctrl.pendingInvites">
<tr ng-click="invite.expanded = !invite.expanded" ng-class="{'expanded': invite.expanded}">
<td>{{invite.email}}</td>
<td>{{invite.name}}</td>
<td class="text-right">
<button class="btn btn-inverse btn-mini " data-clipboard-text="{{invite.url}}" clipboard-button ng-click="ctrl.copyInviteToClipboard($event)">
<i class="fa fa-clipboard"></i> Copy Invite
</button>
&nbsp;
<button class="btn btn-inverse btn-mini">
Details
<i ng-show="!invite.expanded" class="fa fa-caret-right"></i>
<i ng-show="invite.expanded" class="fa fa-caret-down"></i>
</button>
</td>
</tr>
<tr ng-show="invite.expanded">
<td colspan="3">
<a href="{{invite.url}}">{{invite.url}}</a><br><br>
&nbsp;
<button class="btn btn-inverse btn-mini" ng-click="ctrl.revokeInvite(invite, $event)">
<i class="fa fa-remove" style="color: red"></i> Revoke invite
</button>
<span style="padding-left: 15px">
Invited: <em> {{invite.createdOn | date: 'shortDate'}} by {{invite.invitedBy}} </em>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
...@@ -63,64 +63,61 @@ ...@@ -63,64 +63,61 @@
</section> </section>
<div class="editor-row"> <div class="editor-row">
<div class="pull-left"> <div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 1"> <ul>
<h5>Shorter legend names</h5> <li>alias() function to specify a custom series name</li>
<ul> <li>aliasByNode(2) to alias by a specific part of your metric path</li>
<li>alias() function to specify a custom series name</li> <li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li> <li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li> </ul>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li> </div>
</ul>
</div>
<div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 2"> <div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
<h5>Series as parameter</h5> <h5>Series as parameter</h5>
<ul> <ul>
<li>Some graphite functions allow you to have many series arguments</li> <li>Some graphite functions allow you to have many series arguments</li>
<li>Use #[A-Z] to use a graphite query as parameter to a function</li> <li>Use #[A-Z] to use a graphite query as parameter to a function</li>
<li> <li>
Examples: Examples:
<ul> <ul>
<li>asPercent(#A, #B)</li> <li>asPercent(#A, #B)</li>
<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li> <li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li> <li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li> <li>divideSeries(#A, #B)</li>
</ul> </ul>
</li> </li>
<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li> <li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
</ul> </ul>
</div> </div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
<h5>Stacking</h5> <h5>Stacking</h5>
<ul> <ul>
<li>You find the stacking option under Display Styles tab</li> <li>You find the stacking option under Display Styles tab</li>
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li> <li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
</ul> </ul>
</div> </div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 4"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 4">
<h5>Templating</h5> <h5>Templating</h5>
<ul> <ul>
<li>You can use a template variable in place of metric names</li> <li>You can use a template variable in place of metric names</li>
<li>You can use a template variable in place of function parameters</li> <li>You can use a template variable in place of function parameters</li>
<li>You enable the templating feature in Dashboard settings / Feature toggles </li> <li>You enable the templating feature in Dashboard settings / Feature toggles </li>
</ul> </ul>
</div> </div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 5"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 5">
<h5>Max data points</h5> <h5>Max data points</h5>
<ul> <ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li> <li>Every graphite request is issued with a maxDataPoints parameter</li>
<li>Graphite uses this parameter to consolidate the real number of values down to this number</li> <li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
<li>If there are more real values, then by default they will be consolidated using averages</li> <li>If there are more real values, then by default they will be consolidated using averages</li>
<li>This could hide real peaks and max values in your series</li> <li>This could hide real peaks and max values in your series</li>
<li>You can change how point consolidation is made using the consolidateBy graphite function</li> <li>You can change how point consolidation is made using the consolidateBy graphite function</li>
<li>Point consolidation will effect series legend values (min,max,total,current)</li> <li>Point consolidation will effect series legend values (min,max,total,current)</li>
<li>If you override maxDataPoint and set a high value performance can be severely effected</li> <li>If you override maxDataPoint and set a high value performance can be severely effected</li>
</ul> </ul>
</div>
</div> </div>
</div> </div>
...@@ -37,42 +37,39 @@ ...@@ -37,42 +37,39 @@
</section> </section>
<div class="editor-row"> <div class="editor-row">
<div class="pull-left"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1">
<h5>Alias patterns</h5>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1"> <ul>
<h5>Alias patterns</h5> <li>$m = replaced with measurement name</li>
<ul> <li>$measurement = replaced with measurement name</li>
<li>$m = replaced with measurement name</li> <li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li>
<li>$measurement = replaced with measurement name</li> <li>$col = replaced with column name</li>
<li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li> <li>$tag_exampletag = replaced with the value of the <i>exampletag</i> tag</li>
<li>$col = replaced with column name</li> <li>You can also use [[tag_exampletag]] pattern replacement syntax</li>
<li>$tag_exampletag = replaced with the value of the <i>exampletag</i> tag</li> </ul>
<li>You can also use [[tag_exampletag]] pattern replacement syntax</li> </div>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
<h5>Stacking and fill</h5> <h5>Stacking and fill</h5>
<ul> <ul>
<li>When stacking is enabled it is important that points align</li> <li>When stacking is enabled it is important that points align</li>
<li>If there are missing points for one series it can cause gaps or missing bars</li> <li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>You must use fill(0), and select a group by time low limit</li> <li>You must use fill(0), and select a group by time low limit</li>
<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li> <li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li> <li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
</ul> </ul>
</div> </div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
<h5>Group by time</h5> <h5>Group by time</h5>
<ul> <ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li> <li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li> <li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li> <li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
<li>The low limit can only be set in the group by time option below your queries</li> <li>The low limit can only be set in the group by time option below your queries</li>
<li>You set a low limit by adding a greater sign before the interval</li> <li>You set a low limit by adding a greater sign before the interval</li>
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li> <li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul> </ul>
</div>
</div> </div>
</div> </div>
......
...@@ -258,7 +258,6 @@ $popover-border-color: $gray-1; ...@@ -258,7 +258,6 @@ $popover-border-color: $gray-1;
$popover-help-bg: $btn-secondary-bg; $popover-help-bg: $btn-secondary-bg;
$popover-help-color: $text-color; $popover-help-color: $text-color;
// Tooltips and popovers // Tooltips and popovers
// ------------------------- // -------------------------
$tooltipColor: $popover-help-color; $tooltipColor: $popover-help-color;
...@@ -276,6 +275,9 @@ $card-background: linear-gradient(135deg, #2f2f2f, #262626); ...@@ -276,6 +275,9 @@ $card-background: linear-gradient(135deg, #2f2f2f, #262626);
$card-background-hover: linear-gradient(135deg, #343434, #262626); $card-background-hover: linear-gradient(135deg, #343434, #262626);
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
// info box
$info-box-background: linear-gradient(120deg, #142749, #0e203e);
// footer // footer
$footer-link-color: $gray-1; $footer-link-color: $gray-1;
$footer-link-hover: $gray-4; $footer-link-hover: $gray-4;
......
...@@ -300,6 +300,9 @@ $card-background: linear-gradient(135deg, $gray-5, $gray-6); ...@@ -300,6 +300,9 @@ $card-background: linear-gradient(135deg, $gray-5, $gray-6);
$card-background-hover: linear-gradient(135deg, $gray-6, $gray-7); $card-background-hover: linear-gradient(135deg, $gray-6, $gray-7);
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1);
// info box
$info-box-background: linear-gradient(135deg, #f1fbff, #d7ebff);
// footer // footer
$footer-link-color: $gray-3; $footer-link-color: $gray-3;
$footer-link-hover: $dark-5; $footer-link-hover: $dark-5;
......
...@@ -5,15 +5,16 @@ ...@@ -5,15 +5,16 @@
top: -13px; top: -13px;
left: -8px; left: -8px;
font-size: 20px; font-size: 20px;
color: $blue; color: $text-color;
} }
.grafana-info-box { .grafana-info-box {
position: relative; position: relative;
background: $card-background; background: $info-box-background;
box-shadow: $card-shadow; box-shadow: $card-shadow;
padding: 1rem; padding: 1rem;
border-radius: 4px; border-radius: 4px;
margin-bottom: $spacer;
h5 { h5 {
margin-bottom: $spacer; margin-bottom: $spacer;
...@@ -21,5 +22,9 @@ ...@@ -21,5 +22,9 @@
ul { ul {
padding-left: $spacer; padding-left: $spacer;
} }
a {
@extend .external-link;
}
} }
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