diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index a3be5961b..7b75bd453 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -6,10 +6,14 @@
 >
   <ng-template pTemplate="caption">
     <div class="caption">
-      <input
-        type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
-        (keyup)="onSearch($event)"
-      >
+      <div class="ml-auto has-feedback has-clear">
+        <input
+          type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
+          (keyup)="onSearch($event)"
+        >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
+      </div>
     </div>
   </ng-template>
 
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index 4c232e29d..5769c7b53 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -6,11 +6,13 @@
 >
   <ng-template pTemplate="caption">
     <div class="caption">
-      <div class="ml-auto">
+      <div class="ml-auto has-feedback has-clear">
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
           (keyup)="onSearch($event)"
         >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
       </div>
       <a class="ml-2 follow-button" (click)="addDomainsToFollow()" (key.enter)="addDomainsToFollow()">
         <my-global-icon iconName="add"></my-global-icon>
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
index 99d8719a3..592287ea0 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
@@ -14,7 +14,7 @@
 <p-table
   [value]="videoRedundancies" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
-  (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="header">
     <tr>
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
index 99b4e267c..262705603 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
@@ -6,11 +6,13 @@
 >
   <ng-template pTemplate="caption">
     <div class="caption">
-      <div class="ml-auto">
+      <div class="ml-auto has-feedback has-clear">
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
           (keyup)="onSearch($event)"
         >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
       </div>
     </div>
   </ng-template>
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
index aecdca387..17364ae04 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
@@ -6,11 +6,13 @@
 >
   <ng-template pTemplate="caption">
     <div class="caption">
-      <div class="ml-auto">
+      <div class="ml-auto has-feedback has-clear">
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
           (keyup)="onSearch($event)"
         >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
       </div>
       <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
         <my-global-icon iconName="add"></my-global-icon>
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
index 704d43ac4..588d38395 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html
@@ -14,7 +14,7 @@
             alt="Avatar"
           >
           <div>
-            <span class="text-muted">{{ createByString(videoAbuse.reporterAccount) }}</span>
+            <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span>
           </div>
         </a>
         <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' + videoAbuse.reporterAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
@@ -34,7 +34,7 @@
             alt="Avatar"
           >
           <div>
-            <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? createByString(videoAbuse.video.channel.ownerAccount) : '' }}</span>
+            <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? videoAbuse.video.channel.ownerAccount.nameWithHost : '' }}</span>
           </div>
         </a>
         <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:&quot;' +videoAbuse.video.channel.ownerAccount.displayName + '&quot;' }" class="ml-auto text-muted video-details-links" i18n>
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
index 5481915b9..d9cb19845 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts
@@ -1,6 +1,7 @@
-import { Component, ViewEncapsulation, Input } from '@angular/core'
-import { VideoAbuse } from '../../../../../../shared'
+import { Component, Input } from '@angular/core'
 import { Account } from '@app/shared/account/account.model'
+import { Actor } from '@app/shared/actor/actor.model'
+import { ProcessedVideoAbuse } from './video-abuse-list.component'
 
 @Component({
   selector: 'my-video-abuse-details',
@@ -8,9 +9,9 @@ import { Account } from '@app/shared/account/account.model'
   styleUrls: [ '../moderation.component.scss' ]
 })
 export class VideoAbuseDetailsComponent {
-  @Input() videoAbuse: VideoAbuse
+  @Input() videoAbuse: ProcessedVideoAbuse
 
-  createByString (account: Account) {
-    return Account.CREATE_BY_STRING(account.name, account.host)
+  switchToDefaultAvatar ($event: Event) {
+    ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
   }
 }
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
index 2e7b60e2f..ba05073cf 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
@@ -3,25 +3,19 @@
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true"
   [showCurrentPageReport]="true" i18n-currentPageReportTemplate
   currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
-  (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="caption">
     <div class="caption">
       <div class="ml-auto">
-        <div class="input-group">
+        <div class="input-group has-feedback has-clear">
           <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
             <div class="input-group-text" ngbDropdownToggle>
               <span class="caret" aria-haspopup="menu" role="button"></span>
             </div>
 
             <div role="menu" ngbDropdownMenu>
-              <h6 class="dropdown-header" i18n>Filter reports</h6>
-
-              <!-- TODO:
-              <div class="dropdown-item" i18n>Reports opened by admins</div>
-              <div class="dropdown-item" i18n>Reports on videos with multiple reports</div>
-              <div class="dropdown-item" i18n>Unassigned reports</div>
-              -->
+              <h6 class="dropdown-header" i18n>Advanced report filters</h6>
               <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
               <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
               <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
@@ -31,8 +25,10 @@
           </div>
           <input
             type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
-            (keyup)="onSearch($event)"
+            (keyup)="onAbuseSearch($event)"
           >
+          <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
+          <span class="sr-only" i18n>Clear filters</span>
         </div>
       </div>
     </div>
@@ -68,7 +64,7 @@
             >
             <div>
               {{ videoAbuse.reporterAccount.displayName }}
-              <span class="text-muted">{{ createByString(videoAbuse.reporterAccount) }}</span>
+              <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span>
             </div>
           </div>
         </a>
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
index 83d194d52..f54e3dccd 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
@@ -16,9 +16,23 @@ import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
 import { DomSanitizer } from '@angular/platform-browser'
 import { BlocklistService } from '@app/shared/blocklist'
 import { VideoService } from '@app/shared/video/video.service'
-import { ActivatedRoute } from '@angular/router'
+import { ActivatedRoute, Params, Router } from '@angular/router'
 import { filter } from 'rxjs/operators'
 
+export type ProcessedVideoAbuse = VideoAbuse & {
+  moderationCommentHtml?: string,
+  reasonHtml?: string
+  embedHtml?: string
+  updatedAt?: Date
+  // override bare server-side definitions with rich client-side definitions
+  reporterAccount: Account
+  video: VideoAbuse['video'] & {
+    channel: VideoAbuse['video']['channel'] & {
+      ownerAccount: Account
+    }
+  }
+}
+
 @Component({
   selector: 'my-video-abuse-list',
   templateUrl: './video-abuse-list.component.html',
@@ -27,7 +41,7 @@ import { filter } from 'rxjs/operators'
 export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit {
   @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
 
-  videoAbuses: (VideoAbuse & { moderationCommentHtml?: string, reasonHtml?: string })[] = []
+  videoAbuses: ProcessedVideoAbuse[] = []
   totalRecords = 0
   sort: SortMeta = { field: 'createdAt', order: 1 }
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
@@ -44,7 +58,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
     private i18n: I18n,
     private markdownRenderer: MarkdownService,
     private sanitizer: DomSanitizer,
-    private route: ActivatedRoute
+    private route: ActivatedRoute,
+    private router: Router
   ) {
     super()
 
@@ -212,16 +227,25 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
     this.loadData()
   }
 
-  createByString (account: Account) {
-    return Account.CREATE_BY_STRING(account.name, account.host)
+  /* Table filter functions */
+  onAbuseSearch (event: Event) {
+    this.onSearch(event)
+    this.setQueryParams((event.target as HTMLInputElement).value)
   }
 
-  setTableFilter (filter: string) {
-    // FIXME: cannot use ViewChild, so create a component for the filter input
-    const filterInput = document.getElementById('table-filter') as HTMLInputElement
-    if (filterInput) filterInput.value = filter
+  setQueryParams (search: string) {
+    const queryParams: Params = {}
+    if (search) Object.assign(queryParams, { search })
+    this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams })
   }
 
+  resetTableFilter () {
+    this.setTableFilter('')
+    this.setQueryParams('')
+    this.resetSearch()
+  }
+  /* END Table filter functions */
+
   isVideoAbuseAccepted (videoAbuse: VideoAbuse) {
     return videoAbuse.state.id === VideoAbuseState.ACCEPTED
   }
@@ -279,17 +303,20 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV
     }).subscribe(
         async resultList => {
           this.totalRecords = resultList.total
+          this.videoAbuses = []
 
-          this.videoAbuses = resultList.data
-
-          for (const abuse of this.videoAbuses) {
+          for (const abuse of resultList.data) {
             Object.assign(abuse, {
               reasonHtml: await this.toHtml(abuse.reason),
               moderationCommentHtml: await this.toHtml(abuse.moderationComment),
               embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)),
               reporterAccount: new Account(abuse.reporterAccount)
             })
+
+            if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
             if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt
+
+            this.videoAbuses.push(abuse as ProcessedVideoAbuse)
           }
 
         },
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
index b3f7789df..eb194b023 100644
--- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
+++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
@@ -3,15 +3,17 @@
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
   [showCurrentPageReport]="true" i18n-currentPageReportTemplate
   currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blacklisted videos"
-  (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="caption">
     <div class="caption">
-      <div class="ml-auto">
+      <div class="ml-auto has-feedback has-clear">
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
           (keyup)="onSearch($event)"
         >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
       </div>
     </div>
   </ng-template>
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html
index 05d573163..038dfa522 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.html
+++ b/client/src/app/+admin/system/jobs/jobs.component.html
@@ -21,7 +21,7 @@
 <p-table
   [value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" dataKey="uniqId"
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [first]="pagination.start"
-  [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="header">
     <tr>
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.scss b/client/src/app/+admin/users/user-edit/user-password.component.scss
index 5cd93f6af..217d585af 100644
--- a/client/src/app/+admin/users/user-edit/user-password.component.scss
+++ b/client/src/app/+admin/users/user-edit/user-password.component.scss
@@ -16,3 +16,7 @@ input[type=submit] {
 
   margin-top: 10px;
 }
+
+.input-group-append {
+  height: 30px;
+}
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 94c59cb9a..8b71dae79 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
@@ -8,12 +8,12 @@
 </div>
 
 <p-table
-  [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+  [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
   [(selection)]="selectedUsers"
   [showCurrentPageReport]="true" i18n-currentPageReportTemplate
   currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
-  (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="caption">
     <div class="caption">
@@ -25,11 +25,13 @@
         </my-action-dropdown>
       </div>
 
-      <div>
+      <div class="has-feedback has-clear">
         <input
           type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
           (keyup)="onSearch($event)"
         >
+        <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
+        <span class="sr-only" i18n>Clear filters</span>
       </div>
     </div>
   </ng-template>
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
index ba27ee7ff..8f8af655c 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
@@ -19,6 +19,10 @@ my-actor-avatar-info {
   @include peertube-input-group(fit-content);
 }
 
+.input-group-append {
+  height: 30px;
+}
+
 input {
   &[type=text] {
     @include peertube-input-text(340px);
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
index 7d447cdb3..37c6ad6b4 100644
--- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
@@ -1,7 +1,7 @@
 <p-table
   [value]="videoImports" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
   [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
-  (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+  (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
 >
   <ng-template pTemplate="header">
     <tr>
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss
index cc60ef524..e135b5cb4 100644
--- a/client/src/app/+signup/+register/register.component.scss
+++ b/client/src/app/+signup/+register/register.component.scss
@@ -58,6 +58,10 @@
   @include peertube-input-group(400px);
 }
 
+.input-group-append {
+  height: 30px;
+}
+
 input:not([type=submit]) {
   @include peertube-input-text(400px);
 
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts
index 4dd0f5ff3..d4e6cf5f2 100644
--- a/client/src/app/shared/rest/rest-table.ts
+++ b/client/src/app/shared/rest/rest-table.ts
@@ -74,10 +74,29 @@ export abstract class RestTable {
     this.searchStream.next(target.value)
   }
 
-  onPage () {
+  onPage (event: { first: number, rows: number }) {
+    if (this.rowsPerPage !== event.rows) {
+      this.rowsPerPage = event.rows
+      this.pagination = {
+        start: event.first,
+        count: this.rowsPerPage
+      }
+      this.loadData()
+    }
     this.expandedRows = {}
   }
 
+  setTableFilter (filter: string) {
+    // FIXME: cannot use ViewChild, so create a component for the filter input
+    const filterInput = document.getElementById('table-filter') as HTMLInputElement
+    if (filterInput) filterInput.value = filter
+  }
+
+  resetSearch () {
+    this.searchStream.next('')
+    this.setTableFilter('')
+  }
+
   protected abstract loadData (): void
 
   private getSortLocalStorageKey () {
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 62503fc02..bbecd8ba8 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -339,6 +339,11 @@ table {
       .peertube-select-container {
         width: 100% !important;
       }
+
+      .caption input[type=text] {
+        width: unset !important;
+        flex-grow: 1;
+      }
     }
   }
 }
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index 50f1dafed..cb266cc68 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -27,7 +27,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
 }
 
 /* rules for dropdowns excepts when in button group, to avoid impacting the dropdown-toggle */
-.dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown) {
+.dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown):not(.input-group-prepend) {
   z-index: z(dropdown) !important;
 
   &.list-overflow-menu,
@@ -270,10 +270,9 @@ ngb-tooltip-window {
   & > .form-control {
     flex: initial;
   }
-
-  .input-group-prepend,
-  .input-group-append {
-    height: 30px;
+  input.form-control {
+    width: unset !important;
+    flex-grow: 1;
   }
 
   .input-group-prepend + input {
@@ -281,3 +280,35 @@ ngb-tooltip-window {
     border-bottom-left-radius: 0 !important;
   }
 }
+
+.has-feedback.has-clear {
+  position: relative;
+
+  input {
+    padding-right: 1.5rem !important;
+  }
+
+  .form-control-clear {
+    color: rgba(0, 0, 0, 0.4);
+    /*
+     * Enable pointer events as they have been disabled since Bootstrap 3.3
+     * See https://github.com/twbs/bootstrap/pull/14104
+     */
+    pointer-events: all;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: absolute;
+    right: .5rem;
+    height: 95%;
+
+    &:hover {
+      color: rgba(0, 0, 0, 0.7);
+      cursor: pointer;
+    }
+  }
+
+  input:placeholder-shown + .form-control-clear {
+    display: none;
+  }
+}
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss
index eab2b2dfd..d48f2dfc4 100644
--- a/client/src/sass/primeng-custom.scss
+++ b/client/src/sass/primeng-custom.scss
@@ -30,7 +30,8 @@ p-table {
 
     .caption {
       height: 40px;
-      display: flex;
+      width: 100%;
+      display: inline-flex;
       align-items: center;
 
       .input-group-text {
diff --git a/server/models/utils.ts b/server/models/utils.ts
index 3e3825b32..956562e70 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -223,9 +223,12 @@ interface QueryStringFilterPrefixes {
   [key: string]: string | { prefix: string, handler: Function, multiple?: boolean }
 }
 
-function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) {
+function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): {
+  search: string
+  [key: string]: string | number | string[] | number[]
+} {
   const tokens = q // tokenize only if we have a querystring
-    ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean)
+    ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) // split by space unless using double quotes
     : []
 
   // TODO: when Typescript supports Object.fromEntries, replace with the Object method
@@ -252,16 +255,18 @@ function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes)
       }
     })).join(' '),
     // filters defined in prefixes are added under their own name
-    ...objectMap(prefixes, v => {
-      if (typeof v === "string") {
-        return tokens.filter(e => e.startsWith(v)).map(e => e.slice(v.length))
+    ...objectMap(prefixes, p => {
+      if (typeof p === "string") {
+        return tokens.filter(e => e.startsWith(p)).map(e => e.slice(p.length)) // we keep the matched item, and remove its prefix
       } else {
-        const _tokens = tokens.filter(e => e.startsWith(v.prefix)).map(e => e.slice(v.prefix.length)).map(v.handler)
-        return !v.multiple
-          ? _tokens.length > 0
-            ? _tokens[0]
-            : ''
-          : _tokens
+        const _tokens = tokens.filter(e => e.startsWith(p.prefix)).map(e => e.slice(p.prefix.length)).map(p.handler)
+        // multiple is false by default, meaning we usually just keep the first occurence of a given prefix
+        if (!p.multiple && _tokens.length > 0) {
+          return _tokens[0]
+        } else if (!p.multiple) {
+          return ''
+        }
+        return _tokens
       }
     })
   }
diff --git a/shared/models/videos/abuse/video-abuse.model.ts b/shared/models/videos/abuse/video-abuse.model.ts
index bbef7f4f9..f2c2cdc41 100644
--- a/shared/models/videos/abuse/video-abuse.model.ts
+++ b/shared/models/videos/abuse/video-abuse.model.ts
@@ -23,7 +23,7 @@ export interface VideoAbuse {
   }
 
   createdAt: Date
-  updatedAt?: Date
+  updatedAt: Date
 
   count?: number
   nth?: number