feat: more work on #8768

This commit is contained in:
Torkel Ödegaard 2017-07-31 14:39:33 +02:00
parent 23558c61d4
commit 73563d087c
9 changed files with 233 additions and 192 deletions

View File

@ -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,

View File

@ -90,18 +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
ManageUsersLinkUrl string ExternalUserMngLinkUrl string
ManageUsersLinkName string ExternalUserMngLinkName string
ManageUsersInfo string ExternalUserMngInfo string
// Http auth // Http auth
AdminUser string AdminUser string
@ -534,9 +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()
ManageUsersLinkUrl = users.Key("external_manage_link_url").String() ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
ManageUsersLinkName = users.Key("external_manage_link_name").String() ExternalUserMngLinkName = users.Key("external_manage_link_name").String()
ManageUsersInfo = users.Key("external_manage_info").String() ExternalUserMngInfo = users.Key("external_manage_info").String()
// auth // auth
auth := Cfg.Section("auth") auth := Cfg.Section("auth")

View File

@ -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 {
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 {
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 {
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,

View File

@ -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 @@
</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>

View File

@ -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>
<ul>
<li>alias() function to specify a custom series name</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
</ul>
</div>
<div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 1"> <div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
<h5>Shorter legend names</h5> <h5>Series as parameter</h5>
<ul> <ul>
<li>alias() function to specify a custom series name</li> <li>Some graphite functions allow you to have many series arguments</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li> <li>Use #[A-Z] to use a graphite query as parameter to a function</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li> <li>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li> Examples:
</ul> <ul>
</div> <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 - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li>
</ul>
</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>
</div>
<div class="grafana-info-box span8" ng-if="ctrl.panelCtrl.editorHelpIndex === 2"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
<h5>Series as parameter</h5> <h5>Stacking</h5>
<ul> <ul>
<li>Some graphite functions allow you to have many series arguments</li> <li>You find the stacking option under Display Styles tab</li>
<li>Use #[A-Z] to use a graphite query as parameter to a function</li> <li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
<li> </ul>
Examples: </div>
<ul>
<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 - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li>
</ul>
</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>
</div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 4">
<h5>Stacking</h5> <h5>Templating</h5>
<ul> <ul>
<li>You find the stacking option under Display Styles tab</li> <li>You can use a template variable in place of metric names</li>
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li> <li>You can use a template variable in place of function parameters</li>
</ul> <li>You enable the templating feature in Dashboard settings / Feature toggles </li>
</div> </ul>
</div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 4"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 5">
<h5>Templating</h5> <h5>Max data points</h5>
<ul> <ul>
<li>You can use a template variable in place of metric names</li> <li>Every graphite request is issued with a maxDataPoints parameter</li>
<li>You can use a template variable in place of function parameters</li> <li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
<li>You enable the templating feature in Dashboard settings / Feature toggles </li> <li>If there are more real values, then by default they will be consolidated using averages</li>
</ul> <li>This could hide real peaks and max values in your series</li>
</div> <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>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 5"> <li>If you override maxDataPoint and set a high value performance can be severely effected</li>
<h5>Max data points</h5> </ul>
<ul>
<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>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>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>If you override maxDataPoint and set a high value performance can be severely effected</li>
</ul>
</div>
</div> </div>
</div> </div>

View File

@ -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>
<ul>
<li>$m = replaced with measurement name</li>
<li>$measurement = replaced with measurement name</li>
<li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li>
<li>$col = replaced with column name</li>
<li>$tag_exampletag = replaced with the value of the <i>exampletag</i> tag</li>
<li>You can also use [[tag_exampletag]] pattern replacement syntax</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 1"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
<h5>Alias patterns</h5> <h5>Stacking and fill</h5>
<ul> <ul>
<li>$m = replaced with measurement name</li> <li>When stacking is enabled it is important that points align</li>
<li>$measurement = replaced with measurement name</li> <li>If there are missing points for one series it can cause gaps or missing bars</li>
<li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li> <li>You must use fill(0), and select a group by time low limit</li>
<li>$col = replaced with column name</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>$tag_exampletag = replaced with the value of the <i>exampletag</i> tag</li> <li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
<li>You can also use [[tag_exampletag]] pattern replacement syntax</li> </ul>
</ul> </div>
</div>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2"> <div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
<h5>Stacking and fill</h5> <h5>Group by time</h5>
<ul> <ul>
<li>When stacking is enabled it is important that points align</li> <li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
<li>If there are missing points for one series it can cause gaps or missing bars</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>You must use fill(0), and select a group by time low limit</li> <li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</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>The low limit can only be set in the group by time option below your queries</li>
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li> <li>You set a low limit by adding a greater sign before the interval</li>
</ul> <li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</div> </ul>
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<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>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>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>
</ul>
</div>
</div> </div>
</div> </div>

View File

@ -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);
$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;

View File

@ -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;

View File

@ -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 @@
ul { ul {
padding-left: $spacer; padding-left: $spacer;
} }
a {
@extend .external-link;
}
} }