mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ux: org user management changes
This commit is contained in:
parent
cacbcb9c99
commit
ccbd18006e
@ -40,7 +40,8 @@ func (hs *HttpServer) registerRoutes() {
|
|||||||
r.Get("/datasources/", reqSignedIn, Index)
|
r.Get("/datasources/", reqSignedIn, Index)
|
||||||
r.Get("/datasources/new", reqSignedIn, Index)
|
r.Get("/datasources/new", reqSignedIn, Index)
|
||||||
r.Get("/datasources/edit/*", 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("/org/apikeys/", reqSignedIn, Index)
|
||||||
r.Get("/dashboard/import/", reqSignedIn, Index)
|
r.Get("/dashboard/import/", reqSignedIn, Index)
|
||||||
r.Get("/configuration", reqGrafanaAdmin, Index)
|
r.Get("/configuration", reqGrafanaAdmin, Index)
|
||||||
|
@ -6,7 +6,7 @@ type AddInviteForm struct {
|
|||||||
LoginOrEmail string `json:"loginOrEmail" binding:"Required"`
|
LoginOrEmail string `json:"loginOrEmail" binding:"Required"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role m.RoleType `json:"role" binding:"Required"`
|
Role m.RoleType `json:"role" binding:"Required"`
|
||||||
SkipEmails bool `json:"skipEmails"`
|
SendEmail bool `json:"sendEmail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InviteInfo struct {
|
type InviteInfo struct {
|
||||||
|
@ -61,7 +61,7 @@ func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send invite email
|
// send invite email
|
||||||
if !inviteDto.SkipEmails && util.IsEmail(inviteDto.LoginOrEmail) {
|
if inviteDto.SendEmail && util.IsEmail(inviteDto.LoginOrEmail) {
|
||||||
emailCmd := m.SendEmailCommand{
|
emailCmd := m.SendEmailCommand{
|
||||||
To: []string{inviteDto.LoginOrEmail},
|
To: []string{inviteDto.LoginOrEmail},
|
||||||
Template: "new_user_invite.html",
|
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)
|
return ApiError(500, "Error while trying to create org user", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if !inviteDto.SkipEmails && util.IsEmail(user.Email) {
|
if inviteDto.SendEmail && util.IsEmail(user.Email) {
|
||||||
emailCmd := m.SendEmailCommand{
|
emailCmd := m.SendEmailCommand{
|
||||||
To: []string{user.Email},
|
To: []string{user.Email},
|
||||||
Template: "invited_to_org.html",
|
Template: "invited_to_org.html",
|
||||||
|
@ -27,9 +27,8 @@ export class NavModel {
|
|||||||
export class NavModelSrv {
|
export class NavModelSrv {
|
||||||
navItems: any;
|
navItems: any;
|
||||||
|
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private contextSrv) {
|
constructor() {
|
||||||
this.navItems = config.bootData.navTree;
|
this.navItems = config.bootData.navTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,94 +80,6 @@ export class NavModelSrv {
|
|||||||
main: node
|
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);
|
coreModule.service('navModelSrv', NavModelSrv);
|
||||||
|
@ -109,9 +109,10 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/org/users/new', {
|
.when('/org/users/invite', {
|
||||||
templateUrl: 'public/app/features/org/partials/invite.html',
|
templateUrl: 'public/app/features/org/partials/invite.html',
|
||||||
controller : 'UserInviteCtrl',
|
controller : 'UserInviteCtrl',
|
||||||
|
controllerAs: 'ctrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadOrgBundle,
|
||||||
})
|
})
|
||||||
.when('/org/apikeys', {
|
.when('/org/apikeys', {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import Remarkable from 'remarkable';
|
import Remarkable from 'remarkable';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export class OrgUsersCtrl {
|
export class OrgUsersCtrl {
|
||||||
|
unfiltered: any;
|
||||||
user: any;
|
|
||||||
users: any;
|
users: any;
|
||||||
pendingInvites: any;
|
pendingInvites: any;
|
||||||
editor: any;
|
editor: any;
|
||||||
@ -12,21 +12,18 @@ export class OrgUsersCtrl {
|
|||||||
externalUserMngLinkUrl: string;
|
externalUserMngLinkUrl: string;
|
||||||
externalUserMngLinkName: string;
|
externalUserMngLinkName: string;
|
||||||
externalUserMngInfo: string;
|
externalUserMngInfo: string;
|
||||||
addUsersBtnName: string;
|
canInvite: boolean;
|
||||||
|
searchQuery: string;
|
||||||
|
showInvites: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $scope, private backendSrv, navModelSrv, $sce) {
|
constructor(private $scope, private backendSrv, navModelSrv, $sce) {
|
||||||
this.user = {
|
|
||||||
loginOrEmail: '',
|
|
||||||
role: 'Viewer',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
this.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
||||||
|
|
||||||
this.get();
|
this.get();
|
||||||
this.editor = { index: 0 };
|
|
||||||
this.externalUserMngLinkUrl = config.externalUserMngLinkUrl;
|
this.externalUserMngLinkUrl = config.externalUserMngLinkUrl;
|
||||||
this.externalUserMngLinkName = config.externalUserMngLinkName;
|
this.externalUserMngLinkName = config.externalUserMngLinkName;
|
||||||
|
this.canInvite = !config.disableLoginForm && !config.externalUserMngLinkName;
|
||||||
|
|
||||||
// render external user management info markdown
|
// render external user management info markdown
|
||||||
if (config.externalUserMngInfo) {
|
if (config.externalUserMngInfo) {
|
||||||
@ -34,21 +31,13 @@ export class OrgUsersCtrl {
|
|||||||
linkTarget: '__blank',
|
linkTarget: '__blank',
|
||||||
}).render(config.externalUserMngInfo);
|
}).render(config.externalUserMngInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addUsersBtnName = this.getAddUserBtnName();
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddUserBtnName(): string {
|
|
||||||
if (this.externalUserMngLinkName) {
|
|
||||||
return this.externalUserMngLinkName;
|
|
||||||
}
|
|
||||||
return "Invite User";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
this.backendSrv.get('/api/org/users')
|
this.backendSrv.get('/api/org/users')
|
||||||
.then((users) => {
|
.then((users) => {
|
||||||
this.users = users;
|
this.users = users;
|
||||||
|
this.unfiltered = users;
|
||||||
});
|
});
|
||||||
this.backendSrv.get('/api/org/invites')
|
this.backendSrv.get('/api/org/invites')
|
||||||
.then((pendingInvites) => {
|
.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) {
|
updateOrgUser(user) {
|
||||||
this.backendSrv.patch('/api/org/users/' + user.userId, user);
|
this.backendSrv.patch('/api/org/users/' + user.userId, user);
|
||||||
}
|
}
|
||||||
@ -74,38 +70,22 @@ export class OrgUsersCtrl {
|
|||||||
|
|
||||||
removeUserConfirmed(user) {
|
removeUserConfirmed(user) {
|
||||||
this.backendSrv.delete('/api/org/users/' + user.userId)
|
this.backendSrv.delete('/api/org/users/' + user.userId)
|
||||||
.then(this.get.bind(this));
|
.then(this.get.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
revokeInvite(invite, evt) {
|
revokeInvite(invite, evt) {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
this.backendSrv.patch('/api/org/invites/' + invite.code + '/revoke')
|
this.backendSrv.patch('/api/org/invites/' + invite.code + '/revoke')
|
||||||
.then(this.get.bind(this));
|
.then(this.get.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
copyInviteToClipboard(evt) {
|
copyInviteToClipboard(evt) {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
getInviteUrl(invite) {
|
getInviteUrl(invite) {
|
||||||
return invite.url;
|
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.controller('OrgUsersCtrl', OrgUsersCtrl);
|
coreModule.controller('OrgUsersCtrl', OrgUsersCtrl);
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
<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>
|
|
@ -1,49 +1,35 @@
|
|||||||
<page-header model="navModel"></page-header>
|
<page-header model="ctrl.navModel"></page-header>
|
||||||
|
|
||||||
<div class="page-container page-body" ng-cloak>
|
<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">
|
<h2 class="page-sub-heading">Invite User</h2>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="gf-form-inline gf-form-group">
|
<div class="p-b-2">
|
||||||
<div class="gf-form" style="margin-right:.25rem">
|
Send invite or add existing Grafana user to the organization
|
||||||
<a class="btn btn-inverse gf-form-button" ng-click="addInvite()">
|
<span class="highlight-word">{{contextSrv.user.orgName}}</span>
|
||||||
<i class="fa fa-plus"></i>
|
</div>
|
||||||
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="gf-form-button-row">
|
<form name="ctrl.inviteForm">
|
||||||
<button type="submit" class="btn btn-success" ng-click="sendInvites();">Invite Users</button>
|
<div class="gf-form-group">
|
||||||
<a class="btn-text" href="org/users">Cancel</a>
|
<div class="gf-form max-width-30">
|
||||||
</div>
|
<span class="gf-form-label width-10">Email or Username</span>
|
||||||
<div class="clearfix"></div>
|
<input type="text" ng-model="ctrl.invite.loginOrEmail" required class="gf-form-input" placeholder="email@test.com">
|
||||||
</form>
|
</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>
|
</div>
|
||||||
|
@ -1,50 +1,26 @@
|
|||||||
<!-- <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>
|
<page-header model="ctrl.navModel"></page-header>
|
||||||
|
|
||||||
<div class="page-container page-body">
|
<div class="page-container page-body">
|
||||||
<div class="page-action-bar">
|
<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>
|
<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}})
|
Pending Invites ({{ctrl.pendingInvites.length}})
|
||||||
</button>
|
</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>
|
<i class="fa fa-plus"></i>
|
||||||
<span>{{ctrl.addUsersBtnName}}</span>
|
<span>Invite</span>
|
||||||
</a>
|
</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>
|
<i class="fa fa-external-link-square"></i>
|
||||||
{{ctrl.addUsersBtnName}}
|
{{ctrl.externalUserMngLinkName}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -52,7 +28,7 @@
|
|||||||
<span ng-bind-html="ctrl.externalUserMngInfo"></span>
|
<span ng-bind-html="ctrl.externalUserMngInfo"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="ctrl.editor.index === 0" class="tab-content">
|
<div ng-hide="ctrl.showInvites">
|
||||||
<table class="filter-table form-inline">
|
<table class="filter-table form-inline">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -89,46 +65,32 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="ctrl.editor.index === 1">
|
<div ng-if="ctrl.showInvites">
|
||||||
<table class="filter-table form-inline">
|
<table class="filter-table form-inline">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
<th style="width: 34px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody ng-repeat="invite in ctrl.pendingInvites">
|
<tr ng-repeat="invite in ctrl.pendingInvites">
|
||||||
<tr ng-click="invite.expanded = !invite.expanded" ng-class="{'expanded': invite.expanded}">
|
<td>{{invite.email}}</td>
|
||||||
<td>{{invite.email}}</td>
|
<td>{{invite.name}}</td>
|
||||||
<td>{{invite.name}}</td>
|
<td class="text-right">
|
||||||
<td class="text-right">
|
<button class="btn btn-inverse btn-mini" clipboard-button="ctrl.getInviteUrl(invite)" ng-click="ctrl.copyInviteToClipboard($event)">
|
||||||
<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
|
||||||
<i class="fa fa-clipboard"></i> Copy Invite
|
</button>
|
||||||
</button>
|
|
||||||
|
</td>
|
||||||
<button class="btn btn-inverse btn-mini">
|
<td>
|
||||||
Details
|
<button class="btn btn-danger btn-mini" ng-click="ctrl.revokeInvite(invite, $event)">
|
||||||
<i ng-show="!invite.expanded" class="fa fa-caret-right"></i>
|
<i class="fa fa-remove"></i>
|
||||||
<i ng-show="invite.expanded" class="fa fa-caret-down"></i>
|
</button>
|
||||||
</button>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</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>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,56 +1,30 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export class UserInviteCtrl {
|
export class UserInviteCtrl {
|
||||||
|
navModel: any;
|
||||||
|
invite: any;
|
||||||
|
inviteForm: any;
|
||||||
|
|
||||||
/** @ngInject **/
|
/** @ngInject **/
|
||||||
constructor($scope, backendSrv, navModelSrv) {
|
constructor(private backendSrv, navModelSrv, private $location) {
|
||||||
$scope.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
this.navModel = navModelSrv.getNav('cfg', 'users', 0);
|
||||||
|
|
||||||
const defaultInvites = [
|
this.invite = {
|
||||||
{name: '', email: '', role: 'Editor'},
|
name: '',
|
||||||
];
|
email: '',
|
||||||
|
role: 'Editor',
|
||||||
$scope.invites = _.cloneDeep(defaultInvites);
|
sendEmail: true,
|
||||||
|
|
||||||
$scope.options = {skipEmails: false};
|
|
||||||
$scope.init = function() { };
|
|
||||||
|
|
||||||
$scope.addInvite = function() {
|
|
||||||
$scope.invites.push({name: '', email: '', role: 'Editor'});
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
$scope.removeInvite = function(invite) {
|
sendInvite() {
|
||||||
$scope.invites = _.without($scope.invites, invite);
|
if (!this.inviteForm.$valid) {
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.resetInvites = function() {
|
return this.backendSrv.post('/api/org/invites', this.invite).then(() => {
|
||||||
$scope.invites = _.cloneDeep(defaultInvites);
|
this.$location.path('org/users/');
|
||||||
};
|
});
|
||||||
|
|
||||||
$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;
|
|
||||||
|
|
||||||
if (index === $scope.invites.length) {
|
|
||||||
$scope.invitesSent();
|
|
||||||
} else {
|
|
||||||
$scope.sendSingleInvite(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="page-action-bar">
|
<div class="page-action-bar">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label">Search</label>
|
<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>
|
||||||
|
|
||||||
<div class="page-action-bar__spacer"></div>
|
<div class="page-action-bar__spacer"></div>
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
.page-body {
|
.page-body {
|
||||||
padding-top: $spacer*2;
|
padding-top: $spacer*2;
|
||||||
|
min-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-heading {
|
.page-heading {
|
||||||
|
Loading…
Reference in New Issue
Block a user