aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-09 17:51:25 +0200
committerChocobozzz <me@florianbigard.com>2018-08-09 17:55:05 +0200
commit141b177db088891e84040f68aa95008fb52f1d44 (patch)
tree20168077514c920f5b4da4696707db55f51b158d
parent63347a0ff966c7863e5b7431effa1cb0668df893 (diff)
downloadPeerTube-141b177db088891e84040f68aa95008fb52f1d44.tar.gz
PeerTube-141b177db088891e84040f68aa95008fb52f1d44.tar.zst
PeerTube-141b177db088891e84040f68aa95008fb52f1d44.zip
Add ability to ban/unban users
-rw-r--r--client/src/app/+admin/admin.module.ts2
-rw-r--r--client/src/app/+admin/users/shared/user.service.ts12
-rw-r--r--client/src/app/+admin/users/user-list/user-ban-modal.component.html32
-rw-r--r--client/src/app/+admin/users/user-list/user-ban-modal.component.scss6
-rw-r--r--client/src/app/+admin/users/user-list/user-ban-modal.component.ts68
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html33
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss18
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts60
-rw-r--r--client/src/app/login/login.component.html1
-rw-r--r--client/src/app/login/login.component.ts2
-rw-r--r--client/src/app/shared/buttons/action-dropdown.component.html23
-rw-r--r--client/src/app/shared/buttons/action-dropdown.component.scss17
-rw-r--r--client/src/app/shared/buttons/action-dropdown.component.ts4
-rw-r--r--client/src/app/shared/forms/form-validators/user-validators.service.ts13
-rw-r--r--client/src/sass/application.scss4
-rw-r--r--client/src/sass/primeng-custom.scss9
-rw-r--r--server/lib/oauth-model.ts2
17 files changed, 270 insertions, 36 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index d7ae2f7f0..04b7ec5c1 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -13,6 +13,7 @@ import { JobService } from './jobs/shared/job.service'
13import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' 13import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users'
14import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses' 14import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
15import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' 15import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
16import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
16 17
17@NgModule({ 18@NgModule({
18 imports: [ 19 imports: [
@@ -33,6 +34,7 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
33 UserCreateComponent, 34 UserCreateComponent,
34 UserUpdateComponent, 35 UserUpdateComponent,
35 UserListComponent, 36 UserListComponent,
37 UserBanModalComponent,
36 38
37 VideoBlacklistComponent, 39 VideoBlacklistComponent,
38 VideoBlacklistListComponent, 40 VideoBlacklistListComponent,
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
index 1af1e4ef2..ad7fb1eee 100644
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ b/client/src/app/+admin/users/shared/user.service.ts
@@ -59,6 +59,18 @@ export class UserService {
59 .pipe(catchError(err => this.restExtractor.handleError(err))) 59 .pipe(catchError(err => this.restExtractor.handleError(err)))
60 } 60 }
61 61
62 banUser (user: User, reason?: string) {
63 const body = reason ? { reason } : {}
64
65 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body)
66 .pipe(catchError(err => this.restExtractor.handleError(err)))
67 }
68
69 unbanUser (user: User) {
70 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {})
71 .pipe(catchError(err => this.restExtractor.handleError(err)))
72 }
73
62 private formatUser (user: User) { 74 private formatUser (user: User) {
63 let videoQuota 75 let videoQuota
64 if (user.videoQuota === -1) { 76 if (user.videoQuota === -1) {
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.html b/client/src/app/+admin/users/user-list/user-ban-modal.component.html
new file mode 100644
index 000000000..b2958caa4
--- /dev/null
+++ b/client/src/app/+admin/users/user-list/user-ban-modal.component.html
@@ -0,0 +1,32 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Ban {{ userToBan.username }}</h4>
4 <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span>
5 </div>
6
7 <div class="modal-body">
8 <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
9 <div class="form-group">
10 <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
11 </textarea>
12 <div *ngIf="formErrors.reason" class="form-error">
13 {{ formErrors.reason }}
14 </div>
15 </div>
16
17 <div i18n>
18 A banned user will no longer be able to login.
19 </div>
20
21 <div class="form-group inputs">
22 <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span>
23
24 <input
25 type="submit" i18n-value value="Ban this user" class="action-button-submit"
26 [disabled]="!form.valid"
27 >
28 </div>
29 </form>
30 </div>
31
32</ng-template> \ No newline at end of file
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss b/client/src/app/+admin/users/user-list/user-ban-modal.component.scss
new file mode 100644
index 000000000..84562f15c
--- /dev/null
+++ b/client/src/app/+admin/users/user-list/user-ban-modal.component.scss
@@ -0,0 +1,6 @@
1@import 'variables';
2@import 'mixins';
3
4textarea {
5 @include peertube-textarea(100%, 60px);
6}
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts b/client/src/app/+admin/users/user-list/user-ban-modal.component.ts
new file mode 100644
index 000000000..444de1c04
--- /dev/null
+++ b/client/src/app/+admin/users/user-list/user-ban-modal.component.ts
@@ -0,0 +1,68 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { FormReactive, User, UserValidatorsService } from '../../../shared'
4import { UserService } from '../shared'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
9
10@Component({
11 selector: 'my-user-ban-modal',
12 templateUrl: './user-ban-modal.component.html',
13 styleUrls: [ './user-ban-modal.component.scss' ]
14})
15export class UserBanModalComponent extends FormReactive implements OnInit {
16 @ViewChild('modal') modal: NgbModal
17 @Output() userBanned = new EventEmitter<User>()
18
19 private userToBan: User
20 private openedModal: NgbModalRef
21
22 constructor (
23 protected formValidatorService: FormValidatorService,
24 private modalService: NgbModal,
25 private notificationsService: NotificationsService,
26 private userService: UserService,
27 private userValidatorsService: UserValidatorsService,
28 private i18n: I18n
29 ) {
30 super()
31 }
32
33 ngOnInit () {
34 this.buildForm({
35 reason: this.userValidatorsService.USER_BAN_REASON
36 })
37 }
38
39 openModal (user: User) {
40 this.userToBan = user
41 this.openedModal = this.modalService.open(this.modal)
42 }
43
44 hideBanUserModal () {
45 this.userToBan = undefined
46 this.openedModal.close()
47 }
48
49 async banUser () {
50 const reason = this.form.value['reason'] || undefined
51
52 this.userService.banUser(this.userToBan, reason)
53 .subscribe(
54 () => {
55 this.notificationsService.success(
56 this.i18n('Success'),
57 this.i18n('User {{username}} banned.', { username: this.userToBan.username })
58 )
59
60 this.userBanned.emit(this.userToBan)
61 this.hideBanUserModal()
62 },
63
64 err => this.notificationsService.error(this.i18n('Error'), err.message)
65 )
66 }
67
68}
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index ef5a6c648..a92fe95ef 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -9,31 +9,50 @@
9 9
10<p-table 10<p-table
11 [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" 11 [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
12 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" 12 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
13> 13>
14 <ng-template pTemplate="header"> 14 <ng-template pTemplate="header">
15 <tr> 15 <tr>
16 <th style="width: 40px"></th>
16 <th i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th> 17 <th i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th>
17 <th i18n>Email</th> 18 <th i18n>Email</th>
18 <th i18n>Video quota</th> 19 <th i18n>Video quota</th>
19 <th i18n>Role</th> 20 <th i18n>Role</th>
20 <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 21 <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
21 <th></th> 22 <th style="width: 50px;"></th>
22 </tr> 23 </tr>
23 </ng-template> 24 </ng-template>
24 25
25 <ng-template pTemplate="body" let-user> 26 <ng-template pTemplate="body" let-expanded="expanded" let-user>
26 <tr> 27
27 <td>{{ user.username }}</td> 28 <tr [ngClass]="{ banned: user.blocked }">
29 <td>
30 <span *ngIf="user.blockedReason" class="expander" [pRowToggler]="user">
31 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
32 </span>
33 </td>
34 <td>
35 {{ user.username }}
36 <span *ngIf="user.blocked" class="banned-info">(banned)</span>
37 </td>
28 <td>{{ user.email }}</td> 38 <td>{{ user.email }}</td>
29 <td>{{ user.videoQuota }}</td> 39 <td>{{ user.videoQuota }}</td>
30 <td>{{ user.roleLabel }}</td> 40 <td>{{ user.roleLabel }}</td>
31 <td>{{ user.createdAt }}</td> 41 <td>{{ user.createdAt }}</td>
32 <td class="action-cell"> 42 <td class="action-cell">
33 <my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown> 43 <my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown>
34 <!--<my-edit-button [routerLink]="getRouterUserEditLink(user)"></my-edit-button>--> 44 </td>
35 <!--<my-delete-button (click)="removeUser(user)"></my-delete-button>--> 45 </tr>
46 </ng-template>
47
48 <ng-template pTemplate="rowexpansion" let-user>
49 <tr class="user-blocked-reason">
50 <td colspan="7">
51 <span i18n class="ban-reason-label">Ban reason:</span>
52 {{ user.blockedReason }}
36 </td> 53 </td>
37 </tr> 54 </tr>
38 </ng-template> 55 </ng-template>
39</p-table> 56</p-table>
57
58<my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal> \ No newline at end of file
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss
index 4fc36e11e..2d11dd7a0 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/users/user-list/user-list.component.scss
@@ -4,3 +4,21 @@
4.add-button { 4.add-button {
5 @include create-button('../../../../assets/images/global/add.svg'); 5 @include create-button('../../../../assets/images/global/add.svg');
6} 6}
7
8my-action-dropdown /deep/ .icon {
9 &.icon-ban {
10 background-image: url('../../../../assets/images/global/edit-black.svg');
11 }
12}
13
14tr.banned {
15 color: red;
16}
17
18.banned-info {
19 font-style: italic;
20}
21
22.ban-reason-label {
23 font-weight: $font-semibold;
24} \ No newline at end of file
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index 3c83859e0..f5f8f3e4a 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -1,4 +1,4 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { SortMeta } from 'primeng/components/common/sortmeta' 3import { SortMeta } from 'primeng/components/common/sortmeta'
4import { ConfirmService } from '../../../core' 4import { ConfirmService } from '../../../core'
@@ -6,6 +6,8 @@ import { RestPagination, RestTable, User } from '../../../shared'
6import { UserService } from '../shared' 6import { UserService } from '../shared'
7import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' 8import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
9import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
10import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
9 11
10@Component({ 12@Component({
11 selector: 'my-user-list', 13 selector: 'my-user-list',
@@ -13,6 +15,8 @@ import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
13 styleUrls: [ './user-list.component.scss' ] 15 styleUrls: [ './user-list.component.scss' ]
14}) 16})
15export class UserListComponent extends RestTable implements OnInit { 17export class UserListComponent extends RestTable implements OnInit {
18 @ViewChild('userBanModal') userBanModal: UserBanModalComponent
19
16 users: User[] = [] 20 users: User[] = []
17 totalRecords = 0 21 totalRecords = 0
18 rowsPerPage = 10 22 rowsPerPage = 10
@@ -20,6 +24,9 @@ export class UserListComponent extends RestTable implements OnInit {
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 24 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21 userActions: DropdownAction<User>[] = [] 25 userActions: DropdownAction<User>[] = []
22 26
27 private userToBan: User
28 private openedModal: NgbModalRef
29
23 constructor ( 30 constructor (
24 private notificationsService: NotificationsService, 31 private notificationsService: NotificationsService,
25 private confirmService: ConfirmService, 32 private confirmService: ConfirmService,
@@ -30,12 +37,22 @@ export class UserListComponent extends RestTable implements OnInit {
30 37
31 this.userActions = [ 38 this.userActions = [
32 { 39 {
33 type: 'edit', 40 label: this.i18n('Edit'),
34 linkBuilder: this.getRouterUserEditLink 41 linkBuilder: this.getRouterUserEditLink
35 }, 42 },
36 { 43 {
37 type: 'delete', 44 label: this.i18n('Delete'),
38 handler: user => this.removeUser(user) 45 handler: user => this.removeUser(user)
46 },
47 {
48 label: this.i18n('Ban'),
49 handler: user => this.openBanUserModal(user),
50 isDisplayed: user => !user.blocked
51 },
52 {
53 label: this.i18n('Unban'),
54 handler: user => this.unbanUser(user),
55 isDisplayed: user => user.blocked
39 } 56 }
40 ] 57 ]
41 } 58 }
@@ -44,6 +61,43 @@ export class UserListComponent extends RestTable implements OnInit {
44 this.loadSort() 61 this.loadSort()
45 } 62 }
46 63
64 hideBanUserModal () {
65 this.userToBan = undefined
66 this.openedModal.close()
67 }
68
69 openBanUserModal (user: User) {
70 if (user.username === 'root') {
71 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
72 return
73 }
74
75 this.userBanModal.openModal(user)
76 }
77
78 onUserBanned () {
79 this.loadData()
80 }
81
82 async unbanUser (user: User) {
83 const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username })
84 const res = await this.confirmService.confirm(message, this.i18n('Unban'))
85 if (res === false) return
86
87 this.userService.unbanUser(user)
88 .subscribe(
89 () => {
90 this.notificationsService.success(
91 this.i18n('Success'),
92 this.i18n('User {{username}} unbanned.', { username: user.username })
93 )
94 this.loadData()
95 },
96
97 err => this.notificationsService.error(this.i18n('Error'), err.message)
98 )
99 }
100
47 async removeUser (user: User) { 101 async removeUser (user: User) {
48 if (user.username === 'root') { 102 if (user.username === 'root') {
49 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) 103 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index fac63d44d..3a6d61327 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -50,7 +50,6 @@
50 </form> 50 </form>
51</div> 51</div>
52 52
53<!--<ng-template #forgotPasswordModal (onShown)="onForgotPasswordModalShown()">-->
54<ng-template #forgotPasswordModal> 53<ng-template #forgotPasswordModal>
55 <div class="modal-header"> 54 <div class="modal-header">
56 <h4 i18n class="modal-title">Forgot your password</h4> 55 <h4 i18n class="modal-title">Forgot your password</h4>
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index 8e8822510..7c0220885 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -69,7 +69,7 @@ export class LoginComponent extends FormReactive implements OnInit {
69 askResetPassword () { 69 askResetPassword () {
70 this.userService.askResetPassword(this.forgotPasswordEmail) 70 this.userService.askResetPassword(this.forgotPasswordEmail)
71 .subscribe( 71 .subscribe(
72 res => { 72 () => {
73 const message = this.i18n( 73 const message = this.i18n(
74 'An email with the reset password instructions will be sent to {{email}}.', 74 'An email with the reset password instructions will be sent to {{email}}.',
75 { email: this.forgotPasswordEmail } 75 { email: this.forgotPasswordEmail }
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html
index c87ba4c82..8b7241379 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/buttons/action-dropdown.component.html
@@ -1,16 +1,17 @@
1<div class="dropdown-root" dropdown container="body" dropup="true" placement="right" role="button"> 1<div class="dropdown-root" ngbDropdown [placement]="placement">
2 <div class="action-button" dropdownToggle> 2 <div class="action-button" ngbDropdownToggle role="button">
3 <span class="icon icon-action"></span> 3 <span class="icon icon-action"></span>
4 </div> 4 </div>
5 5
6 <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> 6 <div ngbDropdownMenu class="dropdown-menu">
7 <li role="menuitem" *ngFor="let action of actions"> 7 <ng-container *ngFor="let action of actions">
8 <my-delete-button *ngIf="action.type === 'delete'" [label]="action.label" (click)="action.handler(entry)"></my-delete-button> 8 <div class="dropdown-item" *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true">
9 <my-edit-button *ngIf="action.type === 'edit'" [label]="action.label" [routerLink]="action.linkBuilder(entry)"></my-edit-button> 9 <a *ngIf="action.linkBuilder" class="dropdown-item" [routerLink]="action.linkBuilder(entry)">{{ action.label }}</a>
10 10
11 <a *ngIf="action.type === 'custom'" class="dropdown-item" href="#" (click)="action.handler(entry)"> 11 <span *ngIf="!action.linkBuilder" class="custom-action" class="dropdown-item" (click)="action.handler(entry)" role="button">
12 <span *ngIf="action.iconClass" class="icon" [ngClass]="action.iconClass"></span> <ng-container>{{ action.label }}</ng-container> 12 {{ action.label }}
13 </a> 13 </span>
14 </li> 14 </div>
15 </ul> 15 </ng-container>
16 </div>
16</div> \ No newline at end of file 17</div> \ No newline at end of file
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss
index cc459b972..615511093 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.scss
+++ b/client/src/app/shared/buttons/action-dropdown.component.scss
@@ -5,17 +5,28 @@
5 @include peertube-button; 5 @include peertube-button;
6 @include grey-button; 6 @include grey-button;
7 7
8 display: inline-block;
9 padding: 0 10px;
10
11 &::after {
12 display: none;
13 }
14
8 &:hover, &:active, &:focus { 15 &:hover, &:active, &:focus {
9 background-color: $grey-color; 16 background-color: $grey-color;
10 } 17 }
11 18
12 display: inline-block;
13 padding: 0 10px;
14
15 .icon-action { 19 .icon-action {
16 @include icon(21px); 20 @include icon(21px);
17 21
18 background-image: url('../../../assets/images/video/more.svg'); 22 background-image: url('../../../assets/images/video/more.svg');
19 top: -1px; 23 top: -1px;
20 } 24 }
25}
26
27.dropdown-menu {
28 .dropdown-item {
29 cursor: pointer;
30 color: #000 !important;
31 }
21} \ No newline at end of file 32} \ No newline at end of file
diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/buttons/action-dropdown.component.ts
index 407d24b80..17f9cc618 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.ts
+++ b/client/src/app/shared/buttons/action-dropdown.component.ts
@@ -1,11 +1,10 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2 2
3export type DropdownAction<T> = { 3export type DropdownAction<T> = {
4 type: 'custom' | 'delete' | 'edit'
5 label?: string 4 label?: string
6 handler?: (T) => any 5 handler?: (T) => any
7 linkBuilder?: (T) => (string | number)[] 6 linkBuilder?: (T) => (string | number)[]
8 iconClass?: string 7 isDisplayed?: (T) => boolean
9} 8}
10 9
11@Component({ 10@Component({
@@ -17,4 +16,5 @@ export type DropdownAction<T> = {
17export class ActionDropdownComponent<T> { 16export class ActionDropdownComponent<T> {
18 @Input() actions: DropdownAction<T>[] = [] 17 @Input() actions: DropdownAction<T>[] = []
19 @Input() entry: T 18 @Input() entry: T
19 @Input() placement = 'left'
20} 20}
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/forms/form-validators/user-validators.service.ts
index 5edae2e38..ec9566ef3 100644
--- a/client/src/app/shared/forms/form-validators/user-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/user-validators.service.ts
@@ -14,6 +14,8 @@ export class UserValidatorsService {
14 readonly USER_DESCRIPTION: BuildFormValidator 14 readonly USER_DESCRIPTION: BuildFormValidator
15 readonly USER_TERMS: BuildFormValidator 15 readonly USER_TERMS: BuildFormValidator
16 16
17 readonly USER_BAN_REASON: BuildFormValidator
18
17 constructor (private i18n: I18n) { 19 constructor (private i18n: I18n) {
18 20
19 this.USER_USERNAME = { 21 this.USER_USERNAME = {
@@ -99,5 +101,16 @@ export class UserValidatorsService {
99 'required': this.i18n('You must to agree with the instance terms in order to registering on it.') 101 'required': this.i18n('You must to agree with the instance terms in order to registering on it.')
100 } 102 }
101 } 103 }
104
105 this.USER_BAN_REASON = {
106 VALIDATORS: [
107 Validators.minLength(3),
108 Validators.maxLength(250)
109 ],
110 MESSAGES: {
111 'minlength': this.i18n('Ban reason must be at least 3 characters long.'),
112 'maxlength': this.i18n('Ban reason cannot be more than 250 characters long.')
113 }
114 }
102 } 115 }
103} 116}
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index b120d4090..ba8701f5c 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -310,8 +310,4 @@ table {
310 } 310 }
311 } 311 }
312 } 312 }
313
314 bs-dropdown-container {
315 z-index: 10000;
316 }
317} 313}
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss
index f06daec5d..674aa649e 100644
--- a/client/src/sass/primeng-custom.scss
+++ b/client/src/sass/primeng-custom.scss
@@ -17,9 +17,12 @@ p-table {
17 td { 17 td {
18 border: 1px solid #E5E5E5 !important; 18 border: 1px solid #E5E5E5 !important;
19 padding-left: 15px !important; 19 padding-left: 15px !important;
20 overflow: hidden !important; 20
21 text-overflow: ellipsis !important; 21 &:not(.action-cell) {
22 white-space: nowrap !important; 22 overflow: hidden !important;
23 text-overflow: ellipsis !important;
24 white-space: nowrap !important;
25 }
23 } 26 }
24 27
25 tr { 28 tr {
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index f13c25795..f159ad6a9 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -1,4 +1,4 @@
1import { AccessDeniedError} from 'oauth2-server' 1import { AccessDeniedError } from 'oauth2-server'
2import { logger } from '../helpers/logger' 2import { logger } from '../helpers/logger'
3import { UserModel } from '../models/account/user' 3import { UserModel } from '../models/account/user'
4import { OAuthClientModel } from '../models/oauth/oauth-client' 4import { OAuthClientModel } from '../models/oauth/oauth-client'