Improve admin tables

This commit is contained in:
Chocobozzz 2018-02-23 14:36:16 +01:00
parent 621d99f53f
commit ab998f7b6d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
18 changed files with 269 additions and 139 deletions

View File

@ -5,6 +5,7 @@
### Features
* Add ability for admin to inject custom JavaScript/CSS
* Add help tooltip on some fields
### Bug fixes

View File

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
import { ConfigComponent, EditCustomConfigComponent } from '@app/+admin/config'
import { ConfigService } from '@app/+admin/config/shared/config.service'
import { TabsModule } from 'ngx-bootstrap/tabs'
import { DataTableModule } from 'primeng/components/datatable/datatable'
import { TableModule } from 'primeng/table'
import { SharedModule } from '../shared'
import { AdminRoutingModule } from './admin-routing.module'
import { AdminComponent } from './admin.component'
@ -19,7 +19,7 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
imports: [
AdminRoutingModule,
TabsModule.forRoot(),
DataTableModule,
TableModule,
SharedModule
],

View File

@ -1,11 +1,26 @@
<p-dataTable
[value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="createdAt" (onLazyLoad)="loadLazy($event)"
<p-table
[value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column>
<p-column field="score" header="Score"></p-column>
<p-column field="follower.name" header="Name"></p-column>
<p-column field="follower.host" header="Host"></p-column>
<p-column field="state" header="State"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
</p-dataTable>
<ng-template pTemplate="header">
<tr>
<th style="width: 60px">ID</th>
<th>Score</th>
<th>Name</th>
<th>Host</th>
<th>State</th>
<th pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-follow>
<tr>
<td>{{ follow.id }}</td>
<td>{{ follow.score }}</td>
<td>{{ follow.follower.name }}</td>
<td>{{ follow.follower.host }}</td>
<td>{{ follow.state }}</td>
<td>{{ follow.createdAt }}</td>
</tr>
</ng-template>
</p-table>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { SortMeta } from 'primeng/primeng'
@ -11,7 +11,7 @@ import { FollowService } from '../shared'
templateUrl: './followers-list.component.html',
styleUrls: [ './followers-list.component.scss' ]
})
export class FollowersListComponent extends RestTable {
export class FollowersListComponent extends RestTable implements OnInit {
followers: AccountFollow[] = []
totalRecords = 0
rowsPerPage = 10
@ -25,6 +25,10 @@ export class FollowersListComponent extends RestTable {
super()
}
ngOnInit () {
this.loadSort()
}
protected loadData () {
this.followService.getFollowers(this.pagination, this.sort)
.subscribe(

View File

@ -1,14 +1,26 @@
<p-dataTable
[value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="createdAt" (onLazyLoad)="loadLazy($event)"
<p-table
[value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column>
<p-column field="following.host" header="Host"></p-column>
<p-column field="state" header="State"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
<p-column styleClass="action-cell">
<ng-template pTemplate="body" let-following="rowData">
<my-delete-button (click)="removeFollowing(following)"></my-delete-button>
</ng-template>
</p-column>
</p-dataTable>
<ng-template pTemplate="header">
<tr>
<th style="width: 60px">ID</th>
<th>Host</th>
<th>State</th>
<th pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-follow>
<tr>
<td>{{ follow.id }}</td>
<td>{{ follow.following.host }}</td>
<td>{{ follow.state }}</td>
<td>{{ follow.createdAt }}</td>
<td class="action-cell">
<my-delete-button (click)="removeFollowing(follow)"></my-delete-button>
</td>
</tr>
</ng-template>
</p-table>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { SortMeta } from 'primeng/primeng'
import { AccountFollow } from '../../../../../../shared/models/actors/follow.model'
@ -10,7 +10,7 @@ import { FollowService } from '../shared'
selector: 'my-followers-list',
templateUrl: './following-list.component.html'
})
export class FollowingListComponent extends RestTable {
export class FollowingListComponent extends RestTable implements OnInit {
following: AccountFollow[] = []
totalRecords = 0
rowsPerPage = 10
@ -25,6 +25,10 @@ export class FollowingListComponent extends RestTable {
super()
}
ngOnInit () {
this.loadSort()
}
async removeFollowing (follow: AccountFollow) {
const res = await this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow')
if (res === false) return

View File

@ -8,20 +8,42 @@
</div>
</div>
<p-dataTable
[value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="createdAt" (onLazyLoad)="loadLazy($event)" [scrollable]="true" [virtualScroll]="true" [scrollHeight]="scrollHeight"
<p-table
[value]="jobs" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" dataKey="id"
sortField="createdAt" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column>
<p-column field="type" header="Type" [style]="{ width: '210px' }"></p-column>
<p-column field="state" header="State" [style]="{ width: '130px' }"></p-column>
<p-column header="Payload">
<ng-template pTemplate="body" let-job="rowData">
<pre>{{ job.data }}</pre>
</ng-template>
</p-column>
<p-column field="createdAt" header="Created date" [sortable]="true" [style]="{ width: '250px' }"></p-column>
<p-column field="updatedAt" header="Updated date" [style]="{ width: '250px' }"></p-column>
</p-dataTable>
<ng-template pTemplate="header">
<tr>
<th style="width: 27px"></th>
<th style="width: 60px">ID</th>
<th style="width: 210px">Type</th>
<th style="width: 130px">State</th>
<th style="width: 250px" pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
<th style="width: 250px">Updated</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-expanded="expanded" let-job>
<tr>
<td>
<span class="expander" [pRowToggler]="job">
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
</span>
</td>
<td>{{ job.id }}</td>
<td>{{ job.type }}</td>
<td>{{ job.state }}</td>
<td>{{ job.createdAt }}</td>
<td>{{ job.updatedAt }}</td>
</tr>
</ng-template>
<ng-template pTemplate="rowexpansion" let-job>
<tr>
<td colspan="6">
<pre>{{ job.data }}</pre>
</td>
</tr>
</ng-template>
</p-table>

View File

@ -14,11 +14,13 @@ import { RestExtractor } from '../../../shared/rest/rest-extractor.service'
styleUrls: [ './jobs-list.component.scss' ]
})
export class JobsListComponent extends RestTable implements OnInit {
private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state'
jobState: JobState = 'inactive'
jobStates: JobState[] = [ 'active', 'complete', 'failed', 'inactive', 'delayed' ]
jobs: Job[] = []
totalRecords = 0
rowsPerPage = 20
totalRecords: number
rowsPerPage = 10
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
scrollHeight = ''
@ -32,12 +34,16 @@ export class JobsListComponent extends RestTable implements OnInit {
}
ngOnInit () {
// 270 -> headers + footer...
// 380 -> headers + footer...
this.scrollHeight = (viewportHeight() - 380) + 'px'
this.loadJobState()
this.loadSort()
}
onJobStateChanged () {
this.loadData()
this.saveJobState()
}
protected loadData () {
@ -52,4 +58,14 @@ export class JobsListComponent extends RestTable implements OnInit {
err => this.notificationsService.error('Error', err.message)
)
}
private loadJobState () {
const result = localStorage.getItem(JobsListComponent.JOB_STATE_LOCAL_STORAGE_STATE)
if (result) this.jobState = result as JobState
}
private saveJobState () {
localStorage.setItem(JobsListComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState)
}
}

View File

@ -7,20 +7,32 @@
</a>
</div>
<p-dataTable
[value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="id" (onLazyLoad)="loadLazy($event)"
<p-table
[value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [sortable]="true" [style]="{ width: '60px' }"></p-column>
<p-column field="username" header="Username" [sortable]="true"></p-column>
<p-column field="email" header="Email"></p-column>
<p-column field="videoQuota" header="Video quota"></p-column>
<p-column field="roleLabel" header="Role"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
<p-column styleClass="action-cell">
<ng-template pTemplate="body" let-user="rowData">
<my-edit-button [routerLink]="getRouterUserEditLink(user)"></my-edit-button>
<my-delete-button (click)="removeUser(user)"></my-delete-button>
</ng-template>
</p-column>
</p-dataTable>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th>
<th>Email</th>
<th>Video quota</th>
<th>Role</th>
<th pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.videoQuota }}</td>
<td>{{ user.roleLabel }}</td>
<td>{{ user.createdAt }}</td>
<td class="action-cell">
<my-edit-button [routerLink]="getRouterUserEditLink(user)"></my-edit-button>
<my-delete-button (click)="removeUser(user)"></my-delete-button>
</td>
</tr>
</ng-template>
</p-table>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { SortMeta } from 'primeng/components/common/sortmeta'
@ -12,11 +12,11 @@ import { UserService } from '../shared'
templateUrl: './user-list.component.html',
styleUrls: [ './user-list.component.scss' ]
})
export class UserListComponent extends RestTable {
export class UserListComponent extends RestTable implements OnInit {
users: User[] = []
totalRecords = 0
rowsPerPage = 10
sort: SortMeta = { field: 'id', order: 1 }
sort: SortMeta = { field: 'createdAt', order: 1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
@ -27,6 +27,10 @@ export class UserListComponent extends RestTable {
super()
}
ngOnInit () {
this.loadSort()
}
async removeUser (user: User) {
if (user.username === 'root') {
this.notificationsService.error('Error', 'You cannot delete root.')

View File

@ -2,18 +2,27 @@
<div class="admin-sub-title">Video abuses list</div>
</div>
<p-dataTable
[value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="id" (onLazyLoad)="loadLazy($event)"
<p-table
[value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [sortable]="true" [style]="{ width: '60px' }"></p-column>
<p-column field="reason" header="Reason"></p-column>
<p-column field="reporterServerHost" header="Reporter server host"></p-column>
<p-column field="reporterUsername" header="Reporter username"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
<p-column header="Video">
<ng-template pTemplate="body" let-videoAbuse="rowData">
<a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoName }}</a>
</ng-template>
</p-column>
</p-dataTable>
<ng-template pTemplate="header">
<tr>
<th>Reason</th>
<th>Reporter</th>
<th pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
<th>Video</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-videoAbuse>
<tr>
<td>{{ videoAbuse.reason }}</td>
<td>{{ videoAbuse.reporterServerHost + '@' + videoAbuse.reporterUsername }}</td>
<td>{{ videoAbuse.createdAt }}</td>
<td>
<a [routerLink]="getRouterVideoLink(videoAbuse.videoUUID)" title="Go to the video">{{ videoAbuse.videoName }}</a>
</td>
</tr>
</ng-template>
</p-table>

View File

@ -15,7 +15,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
videoAbuses: VideoAbuse[] = []
totalRecords = 0
rowsPerPage = 10
sort: SortMeta = { field: 'id', order: 1 }
sort: SortMeta = { field: 'createdAt', order: 1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
@ -26,11 +26,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
}
ngOnInit () {
this.loadData()
this.loadSort()
}
getRouterVideoLink (videoId: number) {
return [ '/videos', videoId ]
getRouterVideoLink (videoUUID: string) {
return [ '/videos', videoUUID ]
}
protected loadData () {

View File

@ -1,23 +1,35 @@
<div class="row">
<div class="content-padding">
<h3>Blacklisted videos</h3>
<p-dataTable
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
sortField="id" (onLazyLoad)="loadLazy($event)"
>
<p-column field="id" header="ID" [sortable]="true"></p-column>
<p-column field="name" header="Name" [sortable]="true"></p-column>
<p-column field="description" header="Description"></p-column>
<p-column field="views" header="Views" [sortable]="true"></p-column>
<p-column field="nsfw" header="NSFW"></p-column>
<p-column field="uuid" header="UUID" [sortable]="true"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
<p-column header="Delete" styleClass="action-cell">
<ng-template pTemplate="body" let-entry="rowData">
<my-delete-button (click)="removeVideoFromBlacklist(entry)"></my-delete-button>
</ng-template>
</p-column>
</p-dataTable>
</div>
<div class="admin-sub-header">
<div class="admin-sub-title">Blacklisted videos</div>
</div>
<p-table
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
<th>Description</th>
<th pSortableColumn="views">Views <p-sortIcon field="views"></p-sortIcon></th>
<th>NSFW</th>
<th>UUID</th>
<th pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-videoBlacklist>
<tr>
<td>{{ videoBlacklist.name }}</td>
<td>{{ videoBlacklist.description }}</td>
<td>{{ videoBlacklist.views }}</td>
<td>{{ videoBlacklist.nsfw }}</td>
<td>{{ videoBlacklist.uuid }}</td>
<td>{{ videoBlacklist.createdAt }}</td>
<td class="action-cell">
<my-delete-button label="Unblacklist" (click)="removeVideoFromBlacklist(videoBlacklist)"></my-delete-button>
</td>
</tr>
</ng-template>
</p-table>

View File

@ -16,7 +16,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
blacklist: BlacklistedVideo[] = []
totalRecords = 0
rowsPerPage = 10
sort: SortMeta = { field: 'id', order: 1 }
sort: SortMeta = { field: 'createdAt', order: 1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
@ -28,13 +28,13 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
}
ngOnInit () {
this.loadData()
this.loadSort()
}
async removeVideoFromBlacklist (entry: BlacklistedVideo) {
const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the video list.'
const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the videos list.'
const res = await this.confirmService.confirm(confirmMessage, 'Remove')
const res = await this.confirmService.confirm(confirmMessage, 'Unblacklist')
if (res === false) return
this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe(

View File

@ -1,4 +1,4 @@
<span class="action-button action-button-delete" >
<span class="icon icon-delete-grey"></span>
Delete
{{ label }}
</span>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, Input } from '@angular/core'
@Component({
selector: 'my-delete-button',
@ -7,4 +7,5 @@ import { Component } from '@angular/core'
})
export class DeleteButtonComponent {
@Input() label = 'Delete'
}

View File

@ -4,13 +4,28 @@ import { SortMeta } from 'primeng/components/common/sortmeta'
import { RestPagination } from './rest-pagination'
export abstract class RestTable {
abstract totalRecords: number
abstract rowsPerPage: number
abstract sort: SortMeta
abstract pagination: RestPagination
private sortLocalStorageKey = 'rest-table-sort-' + this.constructor.name
protected abstract loadData (): void
loadSort () {
const result = localStorage.getItem(this.sortLocalStorageKey)
if (result) {
try {
this.sort = JSON.parse(result)
} catch (err) {
console.error('Cannot load sort of local storage key ' + this.sortLocalStorageKey, err)
}
}
}
loadLazy (event: LazyLoadEvent) {
this.sort = {
order: event.sortOrder,
@ -23,6 +38,11 @@ export abstract class RestTable {
}
this.loadData()
this.saveSort()
}
saveSort () {
localStorage.setItem(this.sortLocalStorageKey, JSON.stringify(this.sort))
}
}

View File

@ -131,21 +131,9 @@ label {
}
// ngprime data table customizations
p-datatable {
p-table {
font-size: 15px !important;
.ui-datatable-scrollable-header {
background-color: #fff !important;
}
.ui-widget-content {
border: none !important;
}
.ui-datatable-virtual-table {
border-top: none !important;
}
td {
border: 1px solid #E5E5E5 !important;
padding-left: 15px !important;
@ -157,23 +145,33 @@ p-datatable {
tr {
background-color: #fff !important;
height: 46px;
}
&:hover {
background-color: #f0f0f0 !important;
}
.ui-table-tbody {
tr {
&:hover {
background-color: #f0f0f0 !important;
}
&:not(:hover) {
.action-cell * {
display: none !important;
&:not(:hover) {
.action-cell * {
display: none !important;
}
}
&:first-child td {
border-top: none !important;
}
&:last-child td {
border-bottom: none !important;
}
}
&:first-child td {
border-top: none !important;
}
&:last-child td {
border-bottom: none !important;
.expander {
cursor: pointer;
position: relative;
top: 1px;
}
}
@ -195,7 +193,7 @@ p-datatable {
}
}
&.ui-state-active {
&.ui-state-highlight {
background-color: #fff !important;
.fa {