mirror of https://github.com/grafana/grafana
feat(invite): more work on invite, basic creation works, added new tab directive from angular-ui and made new tab style, #2353
parent
444807c35b
commit
0ffcce1b5d
@ -0,0 +1,9 @@ |
||||
package dtos |
||||
|
||||
import m "github.com/grafana/grafana/pkg/models" |
||||
|
||||
type AddInviteForm struct { |
||||
Email string `json:"email" binding:"Required"` |
||||
Name string `json:"name"` |
||||
Role m.RoleType `json:"role" binding:"Required"` |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/api/dtos" |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/middleware" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
func GetPendingOrgInvites(c *middleware.Context) Response { |
||||
query := m.GetTempUsersForOrgQuery{OrgId: c.OrgId} |
||||
|
||||
if err := bus.Dispatch(&query); err != nil { |
||||
return ApiError(500, "Failed to get invites from db", err) |
||||
} |
||||
|
||||
return Json(200, query.Result) |
||||
} |
||||
|
||||
func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response { |
||||
if !inviteDto.Role.IsValid() { |
||||
return ApiError(400, "Invalid role specified", nil) |
||||
} |
||||
|
||||
cmd := m.CreateTempUserCommand{} |
||||
cmd.OrgId = c.OrgId |
||||
cmd.Email = inviteDto.Email |
||||
cmd.Name = inviteDto.Name |
||||
cmd.IsInvite = true |
||||
cmd.InvitedByUserId = c.UserId |
||||
cmd.Code = util.GetRandomString(30) |
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil { |
||||
return ApiError(500, "Failed to save invite to database", err) |
||||
} |
||||
|
||||
return ApiSuccess("ok, done!") |
||||
} |
||||
@ -0,0 +1,3 @@ |
||||
<li ng-class="{active: active, disabled: disabled}"> |
||||
<a href ng-click="select()" tab-heading-transclude>{{heading}}</a> |
||||
</li> |
||||
@ -0,0 +1,10 @@ |
||||
<div> |
||||
<ul class="nav nav-{{type || 'tabs'}} nav-tabs-alt" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude></ul> |
||||
<div class="tab-content"> |
||||
<div class="tab-pane" |
||||
ng-repeat="tab in tabs" |
||||
ng-class="{active: tab.active}" |
||||
tab-content-transclude="tab"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
@ -0,0 +1,28 @@ |
||||
|
||||
.nav-tabs-alt { |
||||
border-bottom: @grafanaTriggerBorder; |
||||
padding-left: 10px; |
||||
|
||||
& > li > a { |
||||
.border-radius(3px); |
||||
} |
||||
|
||||
li > a:hover, |
||||
li.active > a, |
||||
li.active > a:focus, |
||||
li.active > a:hover { |
||||
border: @grafanaTriggerBorder; |
||||
background-color: transparent; |
||||
border-bottom: 1px solid @grafanaPanelBackground; |
||||
} |
||||
|
||||
li.disabled > a { |
||||
color: @textColor; |
||||
} |
||||
|
||||
.open .dropdown-toggle { |
||||
background-color: #060606; |
||||
border-color: transparent; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,6 @@ |
||||
define([ |
||||
'angular', |
||||
'../vendor/angular-ui/tabs', |
||||
], function() { |
||||
}); |
||||
|
||||
@ -0,0 +1,293 @@ |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ui.bootstrap.tabs |
||||
* |
||||
* @description |
||||
* AngularJS version of the tabs directive. |
||||
*/ |
||||
|
||||
angular.module('ui.bootstrap.tabs', []) |
||||
|
||||
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { |
||||
var ctrl = this, |
||||
tabs = ctrl.tabs = $scope.tabs = []; |
||||
|
||||
ctrl.select = function(selectedTab) { |
||||
angular.forEach(tabs, function(tab) { |
||||
if (tab.active && tab !== selectedTab) { |
||||
tab.active = false; |
||||
tab.onDeselect(); |
||||
} |
||||
}); |
||||
selectedTab.active = true; |
||||
selectedTab.onSelect(); |
||||
}; |
||||
|
||||
ctrl.addTab = function addTab(tab) { |
||||
tabs.push(tab); |
||||
// we can't run the select function on the first tab
|
||||
// since that would select it twice
|
||||
if (tabs.length === 1 && tab.active !== false) { |
||||
tab.active = true; |
||||
} else if (tab.active) { |
||||
ctrl.select(tab); |
||||
} |
||||
else { |
||||
tab.active = false; |
||||
} |
||||
}; |
||||
|
||||
ctrl.removeTab = function removeTab(tab) { |
||||
var index = tabs.indexOf(tab); |
||||
//Select a new tab if the tab to be removed is selected and not destroyed
|
||||
if (tab.active && tabs.length > 1 && !destroyed) { |
||||
//If this is the last tab, select the previous tab. else, the next tab.
|
||||
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; |
||||
ctrl.select(tabs[newActiveIndex]); |
||||
} |
||||
tabs.splice(index, 1); |
||||
}; |
||||
|
||||
var destroyed; |
||||
$scope.$on('$destroy', function() { |
||||
destroyed = true; |
||||
}); |
||||
}]) |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ui.bootstrap.tabs.directive:tabset |
||||
* @restrict EA |
||||
* |
||||
* @description |
||||
* Tabset is the outer container for the tabs directive |
||||
* |
||||
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs. |
||||
* @param {boolean=} justified Whether or not to use justified styling for the tabs. |
||||
* |
||||
* @example |
||||
<example module="ui.bootstrap"> |
||||
<file name="index.html"> |
||||
<tabset> |
||||
<tab heading="Tab 1"><b>First</b> Content!</tab> |
||||
<tab heading="Tab 2"><i>Second</i> Content!</tab> |
||||
</tabset> |
||||
<hr /> |
||||
<tabset vertical="true"> |
||||
<tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab> |
||||
<tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab> |
||||
</tabset> |
||||
<tabset justified="true"> |
||||
<tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab> |
||||
<tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab> |
||||
</tabset> |
||||
</file> |
||||
</example> |
||||
*/ |
||||
.directive('tabset', function() { |
||||
return { |
||||
restrict: 'EA', |
||||
transclude: true, |
||||
replace: true, |
||||
scope: { |
||||
type: '@' |
||||
}, |
||||
controller: 'TabsetController', |
||||
templateUrl: 'app/partials/bootstrap/tabset.html', |
||||
link: function(scope, element, attrs) { |
||||
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; |
||||
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; |
||||
} |
||||
}; |
||||
}) |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ui.bootstrap.tabs.directive:tab |
||||
* @restrict EA |
||||
* |
||||
* @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. |
||||
* @param {string=} select An expression to evaluate when the tab is selected. |
||||
* @param {boolean=} active A binding, telling whether or not this tab is selected. |
||||
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled. |
||||
* |
||||
* @description |
||||
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. |
||||
* |
||||
* @example |
||||
<example module="ui.bootstrap"> |
||||
<file name="index.html"> |
||||
<div ng-controller="TabsDemoCtrl"> |
||||
<button class="btn btn-small" ng-click="items[0].active = true"> |
||||
Select item 1, using active binding |
||||
</button> |
||||
<button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled"> |
||||
Enable/disable item 2, using disabled binding |
||||
</button> |
||||
<br /> |
||||
<tabset> |
||||
<tab heading="Tab 1">First Tab</tab> |
||||
<tab select="alertMe()"> |
||||
<tab-heading><i class="icon-bell"></i> Alert me!</tab-heading> |
||||
Second Tab, with alert callback and html heading! |
||||
</tab> |
||||
<tab ng-repeat="item in items" |
||||
heading="{{item.title}}" |
||||
disabled="item.disabled" |
||||
active="item.active"> |
||||
{{item.content}} |
||||
</tab> |
||||
</tabset> |
||||
</div> |
||||
</file> |
||||
<file name="script.js"> |
||||
function TabsDemoCtrl($scope) { |
||||
$scope.items = [ |
||||
{ title:"Dynamic Title 1", content:"Dynamic Item 0" }, |
||||
{ title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } |
||||
]; |
||||
|
||||
$scope.alertMe = function() { |
||||
setTimeout(function() { |
||||
alert("You've selected the alert tab!"); |
||||
}); |
||||
}; |
||||
}; |
||||
</file> |
||||
</example> |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ui.bootstrap.tabs.directive:tabHeading |
||||
* @restrict EA |
||||
* |
||||
* @description |
||||
* Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. |
||||
* |
||||
* @example |
||||
<example module="ui.bootstrap"> |
||||
<file name="index.html"> |
||||
<tabset> |
||||
<tab> |
||||
<tab-heading><b>HTML</b> in my titles?!</tab-heading> |
||||
And some content, too! |
||||
</tab> |
||||
<tab> |
||||
<tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading> |
||||
That's right. |
||||
</tab> |
||||
</tabset> |
||||
</file> |
||||
</example> |
||||
*/ |
||||
.directive('tab', ['$parse', '$log', function($parse, $log) { |
||||
return { |
||||
require: '^tabset', |
||||
restrict: 'EA', |
||||
replace: true, |
||||
templateUrl: 'app/partials/bootstrap/tab.html', |
||||
transclude: true, |
||||
scope: { |
||||
active: '=?', |
||||
heading: '@', |
||||
onSelect: '&select', //This callback is called in contentHeadingTransclude
|
||||
//once it inserts the tab's content into the dom
|
||||
onDeselect: '&deselect' |
||||
}, |
||||
controller: function() { |
||||
//Empty controller so other directives can require being 'under' a tab
|
||||
}, |
||||
compile: function(elm, attrs, transclude) { |
||||
return function postLink(scope, elm, attrs, tabsetCtrl) { |
||||
scope.$watch('active', function(active) { |
||||
if (active) { |
||||
tabsetCtrl.select(scope); |
||||
} |
||||
}); |
||||
|
||||
scope.disabled = false; |
||||
if ( attrs.disable ) { |
||||
scope.$parent.$watch($parse(attrs.disable), function(value) { |
||||
scope.disabled = !! value; |
||||
}); |
||||
} |
||||
|
||||
// Deprecation support of "disabled" parameter
|
||||
// fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
|
||||
// This code is duplicated from the lines above to make it easy to remove once
|
||||
// the feature has been completely deprecated
|
||||
if ( attrs.disabled ) { |
||||
$log.warn('Use of "disabled" attribute has been deprecated, please use "disable"'); |
||||
scope.$parent.$watch($parse(attrs.disabled), function(value) { |
||||
scope.disabled = !! value; |
||||
}); |
||||
} |
||||
|
||||
scope.select = function() { |
||||
if ( !scope.disabled ) { |
||||
scope.active = true; |
||||
} |
||||
}; |
||||
|
||||
tabsetCtrl.addTab(scope); |
||||
scope.$on('$destroy', function() { |
||||
tabsetCtrl.removeTab(scope); |
||||
}); |
||||
|
||||
//We need to transclude later, once the content container is ready.
|
||||
//when this link happens, we're inside a tab heading.
|
||||
scope.$transcludeFn = transclude; |
||||
}; |
||||
} |
||||
}; |
||||
}]) |
||||
|
||||
.directive('tabHeadingTransclude', [function() { |
||||
return { |
||||
restrict: 'A', |
||||
require: '^tab', |
||||
link: function(scope, elm, attrs, tabCtrl) { |
||||
scope.$watch('headingElement', function updateHeadingElement(heading) { |
||||
if (heading) { |
||||
elm.html(''); |
||||
elm.append(heading); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
}]) |
||||
|
||||
.directive('tabContentTransclude', function() { |
||||
return { |
||||
restrict: 'A', |
||||
require: '^tabset', |
||||
link: function(scope, elm, attrs) { |
||||
var tab = scope.$eval(attrs.tabContentTransclude); |
||||
|
||||
//Now our tab is ready to be transcluded: both the tab heading area
|
||||
//and the tab content area are loaded. Transclude 'em both.
|
||||
tab.$transcludeFn(tab.$parent, function(contents) { |
||||
angular.forEach(contents, function(node) { |
||||
if (isTabHeading(node)) { |
||||
//Let tabHeadingTransclude know.
|
||||
tab.headingElement = node; |
||||
} else { |
||||
elm.append(node); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
}; |
||||
function isTabHeading(node) { |
||||
return node.tagName && ( |
||||
node.hasAttribute('tab-heading') || |
||||
node.hasAttribute('data-tab-heading') || |
||||
node.tagName.toLowerCase() === 'tab-heading' || |
||||
node.tagName.toLowerCase() === 'data-tab-heading' |
||||
); |
||||
} |
||||
}) |
||||
|
||||
; |
||||
Loading…
Reference in new issue