mirror of
https://github.com/DeviceFarmer/stf.git
synced 2026-04-25 18:25:15 +02:00
add groups feature
This commit is contained in:
18
res/app/settings/users/index.js
Normal file
18
res/app/settings/users/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
require('./users.css')
|
||||
|
||||
module.exports = angular.module('stf.settings.users', [
|
||||
require('stf/app-state').name,
|
||||
require('stf/settings').name,
|
||||
require('stf/util/common').name,
|
||||
require('stf/users').name
|
||||
])
|
||||
.run(['$templateCache', function($templateCache) {
|
||||
$templateCache.put(
|
||||
'settings/users/users.pug', require('./users.pug')
|
||||
)
|
||||
}])
|
||||
.controller('UsersCtrl', require('./users-controller'))
|
||||
229
res/app/settings/users/users-controller.js
Normal file
229
res/app/settings/users/users-controller.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function UsersCtrl(
|
||||
$scope
|
||||
, UsersService
|
||||
, AppState
|
||||
, SettingsService
|
||||
, ItemsPerPageOptionsService
|
||||
, GenericModalService
|
||||
, CommonService
|
||||
) {
|
||||
const usersByEmail = {}
|
||||
const userFields =
|
||||
'email,' +
|
||||
'name,' +
|
||||
'privilege,' +
|
||||
'groups.quotas'
|
||||
|
||||
function addUser(user, timeStamp) {
|
||||
return CommonService.add(
|
||||
$scope.users
|
||||
, usersByEmail
|
||||
, user
|
||||
, 'email'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function updateUser(user, timeStamp) {
|
||||
return CommonService.update(
|
||||
$scope.users
|
||||
, usersByEmail
|
||||
, user
|
||||
, 'email'
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function deleteUser(email, timeStamp) {
|
||||
return CommonService.delete(
|
||||
$scope.users
|
||||
, usersByEmail
|
||||
, email
|
||||
, timeStamp)
|
||||
}
|
||||
|
||||
function initScope() {
|
||||
UsersService.getOboeUsers(userFields, function(user) {
|
||||
addUser(user, -1)
|
||||
})
|
||||
.done(function() {
|
||||
$scope.$digest()
|
||||
if (CommonService.isExisting(usersByEmail[AppState.user.email])) {
|
||||
$scope.adminUser = $scope.users[usersByEmail[AppState.user.email].index]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
SettingsService.bind($scope, {
|
||||
target: 'removingFilters'
|
||||
, source: 'UsersRemovingFilters'
|
||||
, defaultValue: {
|
||||
groupOwner: 'False'
|
||||
}
|
||||
})
|
||||
$scope.users = []
|
||||
$scope.confirmRemove = {value: true}
|
||||
$scope.scopeUsersCtrl = $scope
|
||||
$scope.itemsPerPageOptions = ItemsPerPageOptionsService
|
||||
SettingsService.bind($scope, {
|
||||
target: 'userItemsPerPage'
|
||||
, source: 'userItemsPerPage'
|
||||
, defaultValue: $scope.itemsPerPageOptions[2]
|
||||
})
|
||||
$scope.tmpEnv = {}
|
||||
$scope.nameRegex = /^[0-9a-zA-Z-_. ]{1,50}$/
|
||||
$scope.nameRegexStr = '/^[0-9a-zA-Z-_. ]{1,50}$/'
|
||||
$scope.removingFilterOptions = ['True', 'False', 'Any']
|
||||
|
||||
$scope.mailTo = function(users) {
|
||||
CommonService.copyToClipboard(users.map(function(user) {
|
||||
return user.email
|
||||
})
|
||||
.join(SettingsService.get('emailSeparator')))
|
||||
.url('mailto:?body=*** Paste the email addresses from the clipboard! ***')
|
||||
}
|
||||
|
||||
$scope.removeUser = function(email, askConfirmation) {
|
||||
if (askConfirmation) {
|
||||
GenericModalService.open({
|
||||
message: 'Really delete this user?'
|
||||
, type: 'Warning'
|
||||
, size: 'sm'
|
||||
, cancel: true
|
||||
})
|
||||
.then(function() {
|
||||
CommonService.errorWrapper(
|
||||
UsersService.removeUser
|
||||
, [email, $scope.removingFilters]
|
||||
)
|
||||
})
|
||||
}
|
||||
else {
|
||||
CommonService.errorWrapper(
|
||||
UsersService.removeUser
|
||||
, [email, $scope.removingFilters]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removeUsers = function(search, filteredUsers, askConfirmation) {
|
||||
function removeUsers() {
|
||||
CommonService.errorWrapper(
|
||||
UsersService.removeUsers
|
||||
, search ?
|
||||
[$scope.removingFilters, filteredUsers.map(function(user) { return user.email }).join()] :
|
||||
[$scope.removingFilters]
|
||||
)
|
||||
}
|
||||
|
||||
if (askConfirmation) {
|
||||
GenericModalService.open({
|
||||
message: 'Really delete this selection of users?'
|
||||
, type: 'Warning'
|
||||
, size: 'sm'
|
||||
, cancel: true
|
||||
})
|
||||
.then(function() {
|
||||
removeUsers()
|
||||
})
|
||||
}
|
||||
else {
|
||||
removeUsers()
|
||||
}
|
||||
}
|
||||
|
||||
$scope.conditionForDefaultQuotasSaving = function(formInvalidStatus) {
|
||||
if (formInvalidStatus) {
|
||||
$scope.tmpEnv.defaultQuotasTooltip = 'Bad syntax'
|
||||
return false
|
||||
}
|
||||
if ($scope.tmpEnv.defaultGroupsNumber
|
||||
!== $scope.adminUser.groups.quotas.defaultGroupsNumber ||
|
||||
$scope.tmpEnv.defaultGroupsDuration
|
||||
!== $scope.adminUser.groups.quotas.defaultGroupsDuration ||
|
||||
$scope.tmpEnv.defaultGroupsRepetitions
|
||||
!== $scope.adminUser.groups.quotas.defaultGroupsRepetitions
|
||||
) {
|
||||
$scope.tmpEnv.defaultQuotasTooltip = ''
|
||||
return true
|
||||
}
|
||||
$scope.tmpEnv.defaultQuotasTooltip = 'No change'
|
||||
return false
|
||||
}
|
||||
|
||||
$scope.initTemporaryDefaultQuotas = function() {
|
||||
$scope.tmpEnv.defaultGroupsNumber = $scope.adminUser.groups.quotas.defaultGroupsNumber
|
||||
$scope.tmpEnv.defaultGroupsDuration = $scope.adminUser.groups.quotas.defaultGroupsDuration
|
||||
$scope.tmpEnv.defaultGroupsRepetitions = $scope.adminUser.groups.quotas.defaultGroupsRepetitions
|
||||
$scope.tmpEnv.defaultQuotasTooltip = 'No change'
|
||||
}
|
||||
|
||||
$scope.updateDefaultUserGroupsQuotas = function() {
|
||||
CommonService.errorWrapper(UsersService.updateDefaultUserGroupsQuotas, [
|
||||
$scope.tmpEnv.defaultGroupsNumber
|
||||
, $scope.tmpEnv.defaultGroupsDuration
|
||||
, $scope.tmpEnv.defaultGroupsRepetitions
|
||||
])
|
||||
}
|
||||
|
||||
$scope.updateUserGroupsQuotas = function(user) {
|
||||
CommonService.errorWrapper(UsersService.updateUserGroupsQuotas, [
|
||||
user.email
|
||||
, user.groupsNumber
|
||||
, user.groupsDuration
|
||||
, user.groupsRepetitions
|
||||
])
|
||||
}
|
||||
|
||||
$scope.initTemporaryUser = function() {
|
||||
$scope.tmpEnv.userName = $scope.tmpEnv.userEmail = ''
|
||||
$scope.tmpEnv.userTooltip = 'Bad syntax'
|
||||
}
|
||||
|
||||
$scope.conditionForQuotasSaving = function(user, formInvalidStatus) {
|
||||
if (formInvalidStatus) {
|
||||
user.quotasTooltip = 'Bad syntax'
|
||||
return false
|
||||
}
|
||||
if (user.groupsNumber !== user.groups.quotas.allocated.number ||
|
||||
user.groupsDuration !== user.groups.quotas.allocated.duration ||
|
||||
user.groupsRepetitions !== user.groups.quotas.repetitions) {
|
||||
user.quotasTooltip = ''
|
||||
return true
|
||||
}
|
||||
user.quotasTooltip = 'No change'
|
||||
return false
|
||||
}
|
||||
|
||||
$scope.initTemporaryQuotas = function(user) {
|
||||
user.groupsNumber = user.groups.quotas.allocated.number
|
||||
user.groupsDuration = user.groups.quotas.allocated.duration
|
||||
user.groupsRepetitions = user.groups.quotas.repetitions
|
||||
user.quotasTooltip = 'No change'
|
||||
}
|
||||
|
||||
$scope.createUser = function() {
|
||||
CommonService.errorWrapper(
|
||||
UsersService.createUser
|
||||
, [$scope.tmpEnv.userName, $scope.tmpEnv.userEmail]
|
||||
)
|
||||
}
|
||||
|
||||
$scope.$on('user.settings.users.created', function(event, message) {
|
||||
addUser(message.user, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.users.deleted', function(event, message) {
|
||||
deleteUser(message.user.email, message.timeStamp)
|
||||
})
|
||||
|
||||
$scope.$on('user.settings.users.updated', function(event, message) {
|
||||
updateUser(message.user, message.timeStamp)
|
||||
})
|
||||
|
||||
initScope()
|
||||
}
|
||||
21
res/app/settings/users/users-spec.js
Normal file
21
res/app/settings/users/users-spec.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
describe('UsersCtrl', function() {
|
||||
|
||||
beforeEach(angular.mock.module(require('./index').name))
|
||||
|
||||
var scope, ctrl
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
scope = $rootScope.$new()
|
||||
ctrl = $controller('UsersCtrl', {$scope: scope})
|
||||
}))
|
||||
|
||||
it('should ...', inject(function() {
|
||||
expect(1).toEqual(1)
|
||||
|
||||
}))
|
||||
|
||||
})
|
||||
87
res/app/settings/users/users.css
Normal file
87
res/app/settings/users/users.css
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
.stf-users .selectable {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.stf-pager-users-total-items {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.stf-users .user-creation, .user-default-quotas-item, .user-filters-item, .form-group.user-quotas-item {
|
||||
margin: 0px 10px 15px 15px;
|
||||
}
|
||||
|
||||
.stf-users .user-save, .user-default-quotas-save, .form-group.user-quotas-save {
|
||||
margin: 5px 10px 15px 15px;
|
||||
}
|
||||
|
||||
.stf-users .user-header {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.stf-users .user-filters-items {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.stf-users .user-default-quotas-items, .user-quotas-items {
|
||||
margin: 0px 0px 15px 0px;
|
||||
}
|
||||
|
||||
.stf-users .user-list-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-users .user-list-label {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stf-users input.ng-invalid {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.stf-users .user-list .user-list-items {
|
||||
margin: 10px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.stf-users .user-list .user-line {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
.stf-users .user-list .user-line.user-actions {
|
||||
padding-bottom: 23px;
|
||||
}
|
||||
|
||||
.stf-users .user-list .heading.user-action-body {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.stf-users .user-list-details.selectable a {
|
||||
padding: 0px;
|
||||
border-bottom: none;
|
||||
color: #167FFC;
|
||||
}
|
||||
|
||||
.stf-users .user-list-details {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.stf-users .user-list-name {
|
||||
color: #007aff;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
margin: 2px 0 6px;
|
||||
}
|
||||
|
||||
.stf-users .user-list-id {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
font-size: 10px;
|
||||
margin-bottom: 2px;
|
||||
color: #999999;
|
||||
font-weight: 300;
|
||||
}
|
||||
216
res/app/settings/users/users.pug
Normal file
216
res/app/settings/users/users.pug
Normal file
@@ -0,0 +1,216 @@
|
||||
//
|
||||
Copyright © 2019 code initially contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
//
|
||||
|
||||
.widget-container.fluid-height.stf-users(ng-controller='UsersCtrl')
|
||||
.heading
|
||||
i.fa.fa-user
|
||||
span(translate) User list
|
||||
|
||||
button.btn.btn-primary-outline.pull-right.btn-sm(
|
||||
ng-click='showCreateUser = !showCreateUser; initTemporaryUser()'
|
||||
ng-class='{ "btn-primary-outline": !showCreateUser, "btn-primary": showCreateUser }')
|
||||
i.fa.fa-plus.fa-fw
|
||||
|
||||
a.pull-right.btn.btn-sm(ng-href='')
|
||||
i.fa.fa-question-circle.fa-fw(uib-tooltip='{{"More about Users" | translate}}' tooltip-placement='left')
|
||||
|
||||
.widget-content.padded
|
||||
|
||||
nothing-to-show(icon='fa-user' message='{{"No Users" | translate}}' ng-if='!users.length')
|
||||
|
||||
div(ng-if='users.length')
|
||||
ul.list-group.user-list
|
||||
li.list-group-item(ng-if='showCreateUser')
|
||||
.user-line
|
||||
.heading
|
||||
i.fa.fa-user
|
||||
span(translate) Create new user
|
||||
|
||||
form.form-inline(name='userForm')
|
||||
.form-group.user-creation
|
||||
label.user-list-label(translate) Name
|
||||
input.form-control.input-sm(
|
||||
name='nameForm'
|
||||
uib-tooltip="{{'Regex syntax' | translate}}: {{::nameRegexStr}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
tooltip-enable='userForm.nameForm.$invalid'
|
||||
type='text' ng-model='tmpEnv.userName' ng-pattern="nameRegex" required)
|
||||
|
||||
.form-group.user-creation
|
||||
label.user-list-label(translate) Email
|
||||
input.form-control.input-sm(size='35' type='email' ng-model='tmpEnv.userEmail' required)
|
||||
|
||||
.form-group.user-save
|
||||
button.btn.btn-sm.btn-primary(
|
||||
type='button'
|
||||
ng-click='createUser()'
|
||||
ng-disabled='userForm.$invalid')
|
||||
span(translate) Save
|
||||
|
||||
li.list-group-item
|
||||
.user-line.user-actions
|
||||
form.form-inline.user-header
|
||||
.form-group
|
||||
stf-pager(
|
||||
tooltip-label="{{'User selection' | translate}}"
|
||||
total-items='filteredUsers.length'
|
||||
total-items-style='stf-pager-users-total-items'
|
||||
items-per-page='scopeUsersCtrl.userItemsPerPage'
|
||||
items-per-page-options='itemsPerPageOptions'
|
||||
current-page='scopeUsersCtrl.userCurrentPage'
|
||||
items-search='search')
|
||||
|
||||
button.btn.btn-xs.btn-danger.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Remove the user selection' | translate}}"
|
||||
tooltip-placement='bottom'
|
||||
tooltip-popup-delay='500'
|
||||
ng-disabled="!filteredUsers.length || filteredUsers.length === 1 && filteredUsers[0].privilege === 'admin'"
|
||||
ng-click='removeUsers(search, filteredUsers, confirmRemove.value)')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Remove
|
||||
|
||||
button.btn.btn-xs.btn-success.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Enable/Disable confirmation for user removing' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='confirmRemove.value = !confirmRemove.value'
|
||||
ng-class='{"btn-warning-outline": !confirmRemove.value, "btn-success": confirmRemove.value}')
|
||||
i.fa.fa-lock(ng-if='confirmRemove.value')
|
||||
i.fa.fa-unlock(ng-if='!confirmRemove.value')
|
||||
span(translate) Confirm Remove
|
||||
|
||||
button.btn.btn-xs.btn-danger-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Set filters for user removing' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='showFilters = !showFilters'
|
||||
ng-class='{"btn-danger-outline": !showFilters, "btn-danger": showFilters}')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Filters
|
||||
|
||||
button.btn.btn-xs.btn-primary-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Set groups quotas for new users' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='showDefaultGroupsQuotas = !showDefaultGroupsQuotas; initTemporaryDefaultQuotas()'
|
||||
ng-class='{"btn-primary-outline": !showDefaultGroupsQuotas, "btn-primary": showDefaultGroupsQuotas}')
|
||||
i.fa.fa-object-group
|
||||
span(translate) Default Groups Quotas
|
||||
|
||||
button.btn.btn-xs.btn-primary-outline.pull-right(
|
||||
type='button'
|
||||
uib-tooltip="{{'Write an email to the user selection' | translate}}"
|
||||
ng-disabled='!filteredUsers.length'
|
||||
ng-click='mailTo(filteredUsers)'
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500')
|
||||
i.fa.fa-envelope-o
|
||||
span(translate) Contact Users
|
||||
|
||||
li.list-group-item(ng-if='showFilters')
|
||||
.user-line
|
||||
.heading
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Removing filters
|
||||
|
||||
form.form-inline.user-filters-items
|
||||
.form-group.user-filters-item
|
||||
label.user-list-label(
|
||||
translate
|
||||
uib-tooltip="{{'Filter on user group ownership' | translate}}"
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500') Group Owner
|
||||
select(ng-model='removingFilters.groupOwner' ng-options='option for option in removingFilterOptions')
|
||||
|
||||
li.list-group-item(ng-if='showDefaultGroupsQuotas')
|
||||
.user-line
|
||||
.heading
|
||||
i.fa.fa-object-group
|
||||
span(translate) Default groups quotas
|
||||
|
||||
form.form-inline.user-default-quotas-items(name='dafaultQuotasForm')
|
||||
.form-group.user-default-quotas-item
|
||||
label.user-list-label(translate) Number of groups
|
||||
input.form-control.input-sm(type='number' min='0' ng-model='tmpEnv.defaultGroupsNumber' required)
|
||||
|
||||
.form-group.user-default-quotas-item
|
||||
label.user-list-label Total duration of groups (ms)
|
||||
input.form-control.input-sm(type='number' min='0' ng-model='tmpEnv.defaultGroupsDuration' required)
|
||||
|
||||
.form-group.user-default-quotas-item
|
||||
label.user-list-label(translate) Number of repetitions per group
|
||||
input.form-control.input-sm(type='number' min='0' ng-model='tmpEnv.defaultGroupsRepetitions' required)
|
||||
|
||||
.form-group.user-default-quotas-save
|
||||
button.btn.btn-sm.btn-primary(
|
||||
uib-tooltip='{{tmpEnv.defaultQuotasTooltip | translate}}'
|
||||
tooltip-enable='tmpEnv.defaultQuotasTooltip'
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='updateDefaultUserGroupsQuotas()'
|
||||
ng-disabled='!conditionForDefaultQuotasSaving(defaultQuotasForm.$invalid)')
|
||||
span(translate) Save
|
||||
|
||||
li.list-group-item(ng-repeat="user in users \
|
||||
| filter:search \
|
||||
| orderBy: 'name' \
|
||||
| pagedObjectsFilter:scopeUsersCtrl:'userCurrentPage':'userItemsPerPage':'filteredUsers' \
|
||||
track by user.email")
|
||||
.user-line.user-actions
|
||||
i.fa.fa-user.fa-2x.fa-fw.user-list-icon
|
||||
.user-list-details.selectable
|
||||
a.user-list-name(ng-href="{{::'mailto:' + user.email}}") {{::user.name}}
|
||||
.user-list-id
|
||||
span(translate) Email
|
||||
span(ng-bind-template="{{::': ' + user.email + ' - '}}")
|
||||
span(translate) Privilege
|
||||
span(ng-bind-template="{{::': ' + user.privilege}}")
|
||||
|
||||
button.btn.btn-xs.btn-danger-outline.pull-right(
|
||||
type='button'
|
||||
ng-click='removeUser(user.email, confirmRemove.value)'
|
||||
ng-disabled='user.privilege === "admin"')
|
||||
i.fa.fa-trash-o
|
||||
span(translate) Remove
|
||||
|
||||
button.btn.btn-xs.btn-primary-outline.pull-right(
|
||||
type='button'
|
||||
ng-click='showGroupsQuotas = !showGroupsQuotas; initTemporaryQuotas(user)'
|
||||
ng-class='{"btn-primary-outline": !showGroupsQuotas, "btn-primary": showGroupsQuotas}')
|
||||
i.fa.fa-object-group
|
||||
span(translate) Groups Quotas
|
||||
|
||||
ul.list-group.user-list.user-list-items(ng-if='showGroupsQuotas')
|
||||
li.list-group-item
|
||||
.heading.user-action-body
|
||||
i.fa.fa-object-group
|
||||
span(translate) Groups Quotas
|
||||
|
||||
form.form-inline(name='quotasForm')
|
||||
.form-group.user-quotas-item
|
||||
label.user-list-label(translate) Number of groups
|
||||
input.form-control.input-sm(type='number' min='0' ng-max-length='5' ng-model='user.groupsNumber' required)
|
||||
|
||||
.form-group.user-quotas-item
|
||||
label.user-list-label(translate) Total duration of groups (ms)
|
||||
input.form-control.input-sm(type='number' min='0' ng-model='user.groupsDuration' required)
|
||||
|
||||
.form-group.user-quotas-item
|
||||
label.user-list-label(translate) Number of repetitions per group
|
||||
input.form-control.input-sm(type='number' min='0' ng-model='user.groupsRepetitions' required)
|
||||
|
||||
.form-group.user-quotas-save
|
||||
button.btn.btn-sm.btn-primary(
|
||||
uib-tooltip='{{user.quotasTooltip | translate}}'
|
||||
tooltip-enable='user.quotasTooltip'
|
||||
tooltip-placement='top'
|
||||
tooltip-popup-delay='500'
|
||||
ng-click='updateUserGroupsQuotas(user)'
|
||||
ng-disabled='!conditionForQuotasSaving(user, quotasForm.$invalid)')
|
||||
span(translate) Save
|
||||
Reference in New Issue
Block a user