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
ccbd1800
Commit
ccbd1800
authored
Dec 13, 2017
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ux: org user management changes
parent
cacbcb9c
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
108 additions
and
347 deletions
+108
-347
pkg/api/api.go
+2
-1
pkg/api/dtos/invite.go
+1
-1
pkg/api/org_invite.go
+2
-2
public/app/core/nav_model_srv.ts
+1
-90
public/app/core/routes/routes.ts
+2
-1
public/app/features/org/org_users_ctrl.ts
+19
-39
public/app/features/org/partials/add_user.html
+0
-55
public/app/features/org/partials/invite.html
+29
-43
public/app/features/org/partials/orgUsers.html
+31
-69
public/app/features/org/user_invite_ctrl.ts
+19
-45
public/app/features/plugins/partials/plugin_list.html
+1
-1
public/sass/layout/_page.scss
+1
-0
No files found.
pkg/api/api.go
View file @
ccbd1800
...
...
@@ -40,7 +40,8 @@ func (hs *HttpServer) registerRoutes() {
r
.
Get
(
"/datasources/"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/datasources/new"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/datasources/edit/*"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/org/users/"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/org/users/new"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/org/users/invite"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/org/apikeys/"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/dashboard/import/"
,
reqSignedIn
,
Index
)
r
.
Get
(
"/configuration"
,
reqGrafanaAdmin
,
Index
)
...
...
pkg/api/dtos/invite.go
View file @
ccbd1800
...
...
@@ -6,7 +6,7 @@ type AddInviteForm struct {
LoginOrEmail
string
`json:"loginOrEmail" binding:"Required"`
Name
string
`json:"name"`
Role
m
.
RoleType
`json:"role" binding:"Required"`
S
kipEmails
bool
`json:"skipEmails
"`
S
endEmail
bool
`json:"sendEmail
"`
}
type
InviteInfo
struct
{
...
...
pkg/api/org_invite.go
View file @
ccbd1800
...
...
@@ -61,7 +61,7 @@ func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response
}
// send invite email
if
!
inviteDto
.
SkipEmails
&&
util
.
IsEmail
(
inviteDto
.
LoginOrEmail
)
{
if
inviteDto
.
SendEmail
&&
util
.
IsEmail
(
inviteDto
.
LoginOrEmail
)
{
emailCmd
:=
m
.
SendEmailCommand
{
To
:
[]
string
{
inviteDto
.
LoginOrEmail
},
Template
:
"new_user_invite.html"
,
...
...
@@ -99,7 +99,7 @@ func inviteExistingUserToOrg(c *middleware.Context, user *m.User, inviteDto *dto
return
ApiError
(
500
,
"Error while trying to create org user"
,
err
)
}
else
{
if
!
inviteDto
.
SkipEmails
&&
util
.
IsEmail
(
user
.
Email
)
{
if
inviteDto
.
SendEmail
&&
util
.
IsEmail
(
user
.
Email
)
{
emailCmd
:=
m
.
SendEmailCommand
{
To
:
[]
string
{
user
.
Email
},
Template
:
"invited_to_org.html"
,
...
...
public/app/core/nav_model_srv.ts
View file @
ccbd1800
...
...
@@ -27,9 +27,8 @@ export class NavModel {
export
class
NavModelSrv
{
navItems
:
any
;
/** @ngInject */
constructor
(
private
contextSrv
)
{
constructor
()
{
this
.
navItems
=
config
.
bootData
.
navTree
;
}
...
...
@@ -81,94 +80,6 @@ export class NavModelSrv {
main
:
node
};
}
getDashboardNav
(
dashboard
,
dashNavCtrl
)
{
// special handling for snapshots
if
(
dashboard
.
meta
.
isSnapshot
)
{
return
{
section
:
{
title
:
dashboard
.
title
,
icon
:
'icon-gf icon-gf-snapshot'
},
menu
:
[
{
title
:
'Go to original dashboard'
,
icon
:
'fa fa-fw fa-external-link'
,
url
:
dashboard
.
snapshot
.
originalUrl
,
}
]
};
}
var
menu
=
[];
if
(
dashboard
.
meta
.
canEdit
)
{
menu
.
push
({
title
:
'Settings'
,
icon
:
'fa fa-fw fa-cog'
,
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'settings'
)
});
menu
.
push
({
title
:
'Templating'
,
icon
:
'fa fa-fw fa-code'
,
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'templating'
)
});
menu
.
push
({
title
:
'Annotations'
,
icon
:
'fa fa-fw fa-comment'
,
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'annotations'
)
});
if
(
!
dashboard
.
meta
.
isHome
)
{
menu
.
push
({
title
:
'Version history'
,
icon
:
'fa fa-fw fa-history'
,
clickHandler
:
()
=>
dashNavCtrl
.
openEditView
(
'history'
)
});
}
menu
.
push
({
title
:
'View JSON'
,
icon
:
'fa fa-fw fa-eye'
,
clickHandler
:
()
=>
dashNavCtrl
.
viewJson
()
});
}
if
(
this
.
contextSrv
.
isEditor
&&
!
dashboard
.
editable
)
{
menu
.
push
({
title
:
'Make Editable'
,
icon
:
'fa fa-fw fa-edit'
,
clickHandler
:
()
=>
dashNavCtrl
.
makeEditable
()
});
}
if
(
this
.
contextSrv
.
isEditor
&&
!
dashboard
.
meta
.
isFolder
)
{
menu
.
push
({
title
:
'Save As...'
,
icon
:
'fa fa-fw fa-save'
,
clickHandler
:
()
=>
dashNavCtrl
.
saveDashboardAs
()
});
}
if
(
dashboard
.
meta
.
canSave
)
{
menu
.
push
({
title
:
'Delete'
,
icon
:
'fa fa-fw fa-trash'
,
clickHandler
:
()
=>
dashNavCtrl
.
deleteDashboard
()
});
}
return
{
section
:
{
title
:
dashboard
.
title
,
icon
:
'icon-gf icon-gf-dashboard'
},
menu
:
menu
};
}
}
coreModule
.
service
(
'navModelSrv'
,
NavModelSrv
);
public/app/core/routes/routes.ts
View file @
ccbd1800
...
...
@@ -109,9 +109,10 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs
:
'ctrl'
,
resolve
:
loadOrgBundle
,
})
.
when
(
'/org/users/
new
'
,
{
.
when
(
'/org/users/
invite
'
,
{
templateUrl
:
'public/app/features/org/partials/invite.html'
,
controller
:
'UserInviteCtrl'
,
controllerAs
:
'ctrl'
,
resolve
:
loadOrgBundle
,
})
.
when
(
'/org/apikeys'
,
{
...
...
public/app/features/org/org_users_ctrl.ts
View file @
ccbd1800
import
config
from
'app/core/config'
;
import
coreModule
from
'app/core/core_module'
;
import
Remarkable
from
'remarkable'
;
import
_
from
'lodash'
;
export
class
OrgUsersCtrl
{
user
:
any
;
unfiltered
:
any
;
users
:
any
;
pendingInvites
:
any
;
editor
:
any
;
...
...
@@ -12,21 +12,18 @@ export class OrgUsersCtrl {
externalUserMngLinkUrl
:
string
;
externalUserMngLinkName
:
string
;
externalUserMngInfo
:
string
;
addUsersBtnName
:
string
;
canInvite
:
boolean
;
searchQuery
:
string
;
showInvites
:
boolean
;
/** @ngInject */
constructor
(
private
$scope
,
private
backendSrv
,
navModelSrv
,
$sce
)
{
this
.
user
=
{
loginOrEmail
:
''
,
role
:
'Viewer'
,
};
this
.
navModel
=
navModelSrv
.
getNav
(
'cfg'
,
'users'
,
0
);
this
.
get
();
this
.
editor
=
{
index
:
0
};
this
.
externalUserMngLinkUrl
=
config
.
externalUserMngLinkUrl
;
this
.
externalUserMngLinkName
=
config
.
externalUserMngLinkName
;
this
.
canInvite
=
!
config
.
disableLoginForm
&&
!
config
.
externalUserMngLinkName
;
// render external user management info markdown
if
(
config
.
externalUserMngInfo
)
{
...
...
@@ -34,21 +31,13 @@ export class OrgUsersCtrl {
linkTarget
:
'__blank'
,
}).
render
(
config
.
externalUserMngInfo
);
}
this
.
addUsersBtnName
=
this
.
getAddUserBtnName
();
}
getAddUserBtnName
():
string
{
if
(
this
.
externalUserMngLinkName
)
{
return
this
.
externalUserMngLinkName
;
}
return
"Invite User"
;
}
get
()
{
this
.
backendSrv
.
get
(
'/api/org/users'
)
.
then
((
users
)
=>
{
this
.
users
=
users
;
this
.
unfiltered
=
users
;
});
this
.
backendSrv
.
get
(
'/api/org/invites'
)
.
then
((
pendingInvites
)
=>
{
...
...
@@ -56,6 +45,13 @@ export class OrgUsersCtrl {
});
}
onQueryUpdated
()
{
let
regex
=
new
RegExp
(
this
.
searchQuery
,
'ig'
);
this
.
users
=
_
.
filter
(
this
.
unfiltered
,
item
=>
{
return
regex
.
test
(
item
.
email
)
||
regex
.
test
(
item
.
login
);
});
}
updateOrgUser
(
user
)
{
this
.
backendSrv
.
patch
(
'/api/org/users/'
+
user
.
userId
,
user
);
}
...
...
@@ -74,38 +70,22 @@ export class OrgUsersCtrl {
removeUserConfirmed
(
user
)
{
this
.
backendSrv
.
delete
(
'/api/org/users/'
+
user
.
userId
)
.
then
(
this
.
get
.
bind
(
this
));
.
then
(
this
.
get
.
bind
(
this
));
}
revokeInvite
(
invite
,
evt
)
{
evt
.
stopPropagation
();
this
.
backendSrv
.
patch
(
'/api/org/invites/'
+
invite
.
code
+
'/revoke'
)
.
then
(
this
.
get
.
bind
(
this
));
.
then
(
this
.
get
.
bind
(
this
));
}
copyInviteToClipboard
(
evt
)
{
evt
.
stopPropagation
();
}
getInviteUrl
(
invite
)
{
return
invite
.
url
;
}
openAddUsersView
()
{
var
modalScope
=
this
.
$scope
.
$new
();
modalScope
.
invitesSent
=
this
.
get
.
bind
(
this
);
var
src
=
config
.
disableLoginForm
?
'public/app/features/org/partials/add_user.html'
:
'public/app/features/org/partials/invite.html'
;
this
.
$scope
.
appEvent
(
'show-modal'
,
{
src
:
src
,
modalClass
:
'invite-modal'
,
scope
:
modalScope
});
}
getInviteUrl
(
invite
)
{
return
invite
.
url
;
}
}
coreModule
.
controller
(
'OrgUsersCtrl'
,
OrgUsersCtrl
);
public/app/features/org/partials/add_user.html
deleted
100644 → 0
View file @
cacbcb9c
<div
class=
"modal-body"
ng-controller=
"UserInviteCtrl"
ng-init=
"init()"
>
<div
class=
"modal-header"
>
<h2
class=
"modal-header-title"
>
Add Users
</h2>
<a
class=
"modal-header-close"
ng-click=
"dismiss();"
>
<i
class=
"fa fa-remove"
></i>
</a>
</div>
<div
class=
"modal-content"
>
<div
class=
"modal-tagline p-b-2"
>
Add existing Grafana users to the organization
<span
class=
"highlight-word"
>
{{contextSrv.user.orgName}}
</span>
</div>
<form
name=
"inviteForm"
>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
ng-repeat=
"invite in invites"
>
<div
class=
"gf-form max-width-21"
>
<span
class=
"gf-form-label"
>
Email or Username
</span>
<input
type=
"text"
ng-model=
"invite.loginOrEmail"
required
class=
"gf-form-input"
placeholder=
"email@test.com"
>
</div>
<div
class=
"gf-form max-width-10"
>
<span
class=
"gf-form-label"
>
Role
</span>
<select
ng-model=
"invite.role"
class=
"gf-form-input"
ng-options=
"f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"
>
</select>
</div>
<div
class=
"gf-form gf-size-auto"
>
<a
class=
"gf-form-label pointer"
tabindex=
"1"
ng-click=
"removeInvite(invite)"
>
<i
class=
"fa fa-remove"
></i>
</a>
</div>
</div>
</div>
<div
class=
"gf-form-inline gf-form-group"
>
<div
class=
"gf-form"
>
<a
class=
"btn btn-inverse btn-small"
ng-click=
"addInvite()"
>
<i
class=
"fa fa-plus"
></i>
Add another
</a>
</div>
</div>
<div
class=
"gf-form-button-row"
>
<button
type=
"submit"
class=
"btn btn-success"
ng-click=
"sendInvites();"
>
Add Users
</button>
<a
class=
"btn-text"
ng-click=
"dismiss()"
>
Cancel
</a>
</div>
<div
class=
"clearfix"
></div>
</form>
</div>
</div>
public/app/features/org/partials/invite.html
View file @
ccbd1800
<page-header
model=
"navModel"
></page-header>
<page-header
model=
"
ctrl.
navModel"
></page-header>
<div
class=
"page-container page-body"
ng-cloak
>
<div
class=
"p-b-2"
>
Send invite or add existing Grafana users to the organization
<span
class=
"highlight-word"
>
{{contextSrv.user.orgName}}
</span>
</div>
<form
name=
"inviteForm"
>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
ng-repeat=
"invite in invites"
>
<div
class=
"gf-form max-width-21"
>
<span
class=
"gf-form-label"
>
Email or Username
</span>
<input
type=
"text"
ng-model=
"invite.loginOrEmail"
required
class=
"gf-form-input"
placeholder=
"email@test.com"
>
</div>
<div
class=
"gf-form max-width-14"
>
<span
class=
"gf-form-label"
>
Name
</span>
<input
type=
"text"
ng-model=
"invite.name"
class=
"gf-form-input"
placeholder=
"name (optional)"
>
</div>
<div
class=
"gf-form max-width-10"
>
<span
class=
"gf-form-label"
>
Role
</span>
<select
ng-model=
"invite.role"
class=
"gf-form-input"
ng-options=
"f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"
>
</select>
</div>
<div
class=
"gf-form gf-size-auto"
>
<a
class=
"gf-form-label pointer"
tabindex=
"1"
ng-click=
"removeInvite(invite)"
>
<i
class=
"fa fa-remove"
></i>
</a>
</div>
</div>
</div>
<h2
class=
"page-sub-heading"
>
Invite User
</h2>
<div
class=
"gf-form-inline gf-form-group"
>
<div
class=
"gf-form"
style=
"margin-right:.25rem"
>
<a
class=
"btn btn-inverse gf-form-button"
ng-click=
"addInvite()"
>
<i
class=
"fa fa-plus"
></i>
Invite another
</a>
</div>
<gf-form-switch
class=
"gf-form"
label=
"Skip sending invite email"
checked=
"options.skipEmails"
switch-class=
"max-width-6"
></gf-form-switch>
</div>
<div
class=
"p-b-2"
>
Send invite or add existing Grafana user to the organization
<span
class=
"highlight-word"
>
{{contextSrv.user.orgName}}
</span>
</div>
<div
class=
"gf-form-button-row"
>
<button
type=
"submit"
class=
"btn btn-success"
ng-click=
"sendInvites();"
>
Invite Users
</button>
<a
class=
"btn-text"
href=
"org/users"
>
Cancel
</a>
</div>
<div
class=
"clearfix"
></div>
</form>
<form
name=
"ctrl.inviteForm"
>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form max-width-30"
>
<span
class=
"gf-form-label width-10"
>
Email or Username
</span>
<input
type=
"text"
ng-model=
"ctrl.invite.loginOrEmail"
required
class=
"gf-form-input"
placeholder=
"email@test.com"
>
</div>
<div
class=
"gf-form max-width-30"
>
<span
class=
"gf-form-label width-10"
>
Name
</span>
<input
type=
"text"
ng-model=
"ctrl.invite.name"
class=
"gf-form-input"
placeholder=
"name (optional)"
>
</div>
<div
class=
"gf-form max-width-30"
>
<span
class=
"gf-form-label width-10"
>
Role
</span>
<select
ng-model=
"ctrl.invite.role"
class=
"gf-form-input"
ng-options=
"f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"
>
</select>
</div>
<gf-form-switch
class=
"gf-form"
label=
"Send invite email"
checked=
"ctrl.invite.sendEmail"
label-class=
"width-10"
></gf-form-switch>
<div
class=
"gf-form-button-row"
>
<button
type=
"submit"
class=
"btn btn-success"
ng-click=
"ctrl.sendInvite();"
>
Invite
</button>
<a
class=
"btn btn-inverse"
href=
"org/users"
>
Back
</a>
</div>
</form>
</div>
public/app/features/org/partials/orgUsers.html
View file @
ccbd1800
<!-- <navbar model="ctrl.navModel"></navbar> -->
<!-- -->
<!-- <div class="page-container"> -->
<!-- <div class="page-header"> -->
<!-- <page-h1 model="ctrl.navModel"></page-h1> -->
<!-- -->
<!-- <button class="btn btn-success" ng-click="ctrl.openAddUsersView()" ng-hide="ctrl.externalUserMngLinkUrl"> -->
<!-- <span>{{ctrl.addUsersBtnName}}</span> -->
<!-- </button> -->
<!-- -->
<!-- <div class="page-header-tabs"> -->
<!-- -->
<!-- <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"> -->
<!-- <li class="gf-tabs-item"> -->
<!-- <a class="gf-tabs-link" ng-click="ctrl.editor.index = 0" ng-class="{active: ctrl.editor.index === 0}"> -->
<!-- Users ({{ctrl.users.length}}) -->
<!-- </a> -->
<!-- </li> -->
<!-- <li class="gf-tabs-item" ng-show="ctrl.pendingInvites.length"> -->
<!-- <a class="gf-tabs-link" ng-click="ctrl.editor.index = 1" ng-class="{active: ctrl.editor.index === 1}"> -->
<!-- Pending Invites ({{ctrl.pendingInvites.length}}) -->
<!-- </a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- </div> -->
<page-header
model=
"ctrl.navModel"
></page-header>
<div
class=
"page-container page-body"
>
<div
class=
"page-action-bar"
>
<div
class=
"gf-form"
>
<label
class=
"gf-form-label"
>
Search
</label>
<input
type=
"text"
class=
"gf-form-input width-20"
ng-model=
"ctrl.searchQuery"
ng-change=
"ctrl.onQueryUpdated()"
give-focus=
"true"
placeholder=
"Filter by username or email"
/>
</div>
<div
class=
"page-action-bar__spacer"
></div>
<button
class=
"btn btn-inverse"
ng-show=
"ctrl.pendingInvites.length"
ng-click=
"ctrl.editor.index = 1"
>
<button
class=
"btn btn-inverse"
ng-show=
"ctrl.pendingInvites.length"
ng-click=
"ctrl.showInvites = true"
>
Pending Invites ({{ctrl.pendingInvites.length}})
</button>
<a
class=
"btn btn-success"
href=
"org/users/new"
ng-hide=
"ctrl.externalUserMngLinkUrl"
>
<a
class=
"btn btn-success"
href=
"org/users/invite"
ng-show=
"ctrl.canInvite"
>
<i
class=
"fa fa-plus"
></i>
<span>
{{ctrl.addUsersBtnName}}
</span>
<span>
Invite
</span>
</a>
<a
class=
"btn btn-inverse"
ng-href=
"{{ctrl.externalUserMngLinkUrl}}"
target=
"_blank"
ng-if=
"ctrl.externalUserMngLinkUrl"
>
<a
class=
"btn btn-success"
ng-href=
"{{ctrl.externalUserMngLinkUrl}}"
target=
"_blank"
ng-if=
"ctrl.externalUserMngLinkUrl"
>
<i
class=
"fa fa-external-link-square"
></i>
{{ctrl.
addUsersBtn
Name}}
{{ctrl.
externalUserMngLink
Name}}
</a>
</div>
...
...
@@ -52,7 +28,7 @@
<span
ng-bind-html=
"ctrl.externalUserMngInfo"
></span>
</div>
<div
ng-
if=
"ctrl.editor.index === 0"
class=
"tab-content
"
>
<div
ng-
hide=
"ctrl.showInvites
"
>
<table
class=
"filter-table form-inline"
>
<thead>
<tr>
...
...
@@ -89,46 +65,32 @@
</table>
</div>
<div
ng-if=
"ctrl.
editor.index === 1
"
>
<div
ng-if=
"ctrl.
showInvites
"
>
<table
class=
"filter-table form-inline"
>
<thead>
<tr>
<th>
Email
</th>
<th>
Name
</th>
<th></th>
<th
style=
"width: 34px;"
></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"
clipboard-button=
"ctrl.getInviteUrl(invite)"
ng-click=
"ctrl.copyInviteToClipboard($event)"
>
<i
class=
"fa fa-clipboard"
></i>
Copy Invite
</button>
<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>
<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>
<tr
ng-repeat=
"invite in ctrl.pendingInvites"
>
<td>
{{invite.email}}
</td>
<td>
{{invite.name}}
</td>
<td
class=
"text-right"
>
<button
class=
"btn btn-inverse btn-mini"
clipboard-button=
"ctrl.getInviteUrl(invite)"
ng-click=
"ctrl.copyInviteToClipboard($event)"
>
<i
class=
"fa fa-clipboard"
></i>
Copy Invite
</button>
</td>
<td>
<button
class=
"btn btn-danger btn-mini"
ng-click=
"ctrl.revokeInvite(invite, $event)"
>
<i
class=
"fa fa-remove"
></i>
</button>
</td>
</tr>
</table>
</div>
</div>
public/app/features/org/user_invite_ctrl.ts
View file @
ccbd1800
import
coreModule
from
'app/core/core_module'
;
import
_
from
'lodash'
;
export
class
UserInviteCtrl
{
navModel
:
any
;
invite
:
any
;
inviteForm
:
any
;
/** @ngInject **/
constructor
(
$scope
,
backendSrv
,
navModelSrv
)
{
$scope
.
navModel
=
navModelSrv
.
getNav
(
'cfg'
,
'users'
,
0
);
const
defaultInvites
=
[
{
name
:
''
,
email
:
''
,
role
:
'Editor'
},
];
$scope
.
invites
=
_
.
cloneDeep
(
defaultInvites
);
$scope
.
options
=
{
skipEmails
:
false
};
$scope
.
init
=
function
()
{
};
$scope
.
addInvite
=
function
()
{
$scope
.
invites
.
push
({
name
:
''
,
email
:
''
,
role
:
'Editor'
});
};
$scope
.
removeInvite
=
function
(
invite
)
{
$scope
.
invites
=
_
.
without
(
$scope
.
invites
,
invite
);
};
$scope
.
resetInvites
=
function
()
{
$scope
.
invites
=
_
.
cloneDeep
(
defaultInvites
);
constructor
(
private
backendSrv
,
navModelSrv
,
private
$location
)
{
this
.
navModel
=
navModelSrv
.
getNav
(
'cfg'
,
'users'
,
0
);
this
.
invite
=
{
name
:
''
,
email
:
''
,
role
:
'Editor'
,
sendEmail
:
true
,
};
}
$scope
.
sendInvites
=
function
()
{
if
(
!
$scope
.
inviteForm
.
$valid
)
{
return
;
}
$scope
.
sendSingleInvite
(
0
);
};
$scope
.
invitesSent
=
function
()
{
$scope
.
resetInvites
();
};
$scope
.
sendSingleInvite
=
function
(
index
)
{
var
invite
=
$scope
.
invites
[
index
];
invite
.
skipEmails
=
$scope
.
options
.
skipEmails
;
return
backendSrv
.
post
(
'/api/org/invites'
,
invite
).
finally
(
function
()
{
index
+=
1
;
sendInvite
()
{
if
(
!
this
.
inviteForm
.
$valid
)
{
return
;
}
if
(
index
===
$scope
.
invites
.
length
)
{
$scope
.
invitesSent
();
}
else
{
$scope
.
sendSingleInvite
(
index
);
}
});
};
return
this
.
backendSrv
.
post
(
'/api/org/invites'
,
this
.
invite
).
then
(()
=>
{
this
.
$location
.
path
(
'org/users/'
);
});
}
}
...
...
public/app/features/plugins/partials/plugin_list.html
View file @
ccbd1800
...
...
@@ -4,7 +4,7 @@
<div
class=
"page-action-bar"
>
<div
class=
"gf-form"
>
<label
class=
"gf-form-label"
>
Search
</label>
<input
type=
"text"
class=
"gf-form-input width-20"
ng-model=
"ctrl.searchQuery"
ng-change=
"ctrl.onQueryUpdated()"
/>
<input
type=
"text"
class=
"gf-form-input width-20"
ng-model=
"ctrl.searchQuery"
ng-change=
"ctrl.onQueryUpdated()"
placeholder=
"Filter by name or type"
/>
</div>
<div
class=
"page-action-bar__spacer"
></div>
...
...
public/sass/layout/_page.scss
View file @
ccbd1800
...
...
@@ -36,6 +36,7 @@
.page-body
{
padding-top
:
$spacer
*
2
;
min-height
:
500px
;
}
.page-heading
{
...
...
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