Improve admin users list table
* Fix last login sort with null values * Remember last selected columns * Display last login date by default
This commit is contained in:
parent
3eba7ab815
commit
87a0cac618
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<p-table
|
<p-table
|
||||||
[value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
[value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers"
|
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers"
|
||||||
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
|
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
|
||||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
|
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { SortMeta } from 'primeng/api'
|
import { SortMeta } from 'primeng/api'
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core'
|
import { Component, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
||||||
import { getAPIHost } from '@app/helpers'
|
import { getAPIHost } from '@app/helpers'
|
||||||
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { Actor, DropdownAction } from '@app/shared/shared-main'
|
import { Actor, DropdownAction } from '@app/shared/shared-main'
|
||||||
|
@ -22,6 +22,8 @@ type UserForList = User & {
|
||||||
styleUrls: [ './user-list.component.scss' ]
|
styleUrls: [ './user-list.component.scss' ]
|
||||||
})
|
})
|
||||||
export class UserListComponent extends RestTable implements OnInit {
|
export class UserListComponent extends RestTable implements OnInit {
|
||||||
|
private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
|
||||||
|
|
||||||
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
||||||
|
|
||||||
users: (User & { accountMutedStatus: AccountMutedStatus })[] = []
|
users: (User & { accountMutedStatus: AccountMutedStatus })[] = []
|
||||||
|
@ -56,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit {
|
||||||
|
|
||||||
requiresEmailVerification = false
|
requiresEmailVerification = false
|
||||||
|
|
||||||
private _selectedColumns: string[]
|
private _selectedColumns: string[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
|
@ -66,7 +68,8 @@ export class UserListComponent extends RestTable implements OnInit {
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private auth: AuthService,
|
private auth: AuthService,
|
||||||
private blocklist: BlocklistService,
|
private blocklist: BlocklistService,
|
||||||
private userAdminService: UserAdminService
|
private userAdminService: UserAdminService,
|
||||||
|
private peertubeLocalStorage: LocalStorageService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
@ -76,11 +79,13 @@ export class UserListComponent extends RestTable implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedColumns () {
|
get selectedColumns () {
|
||||||
return this._selectedColumns
|
return this._selectedColumns || []
|
||||||
}
|
}
|
||||||
|
|
||||||
set selectedColumns (val: string[]) {
|
set selectedColumns (val: string[]) {
|
||||||
this._selectedColumns = val
|
this._selectedColumns = val
|
||||||
|
|
||||||
|
this.saveSelectedColumns()
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
@ -126,14 +131,35 @@ export class UserListComponent extends RestTable implements OnInit {
|
||||||
{ id: 'role', label: $localize`Role` },
|
{ id: 'role', label: $localize`Role` },
|
||||||
{ id: 'email', label: $localize`Email` },
|
{ id: 'email', label: $localize`Email` },
|
||||||
{ id: 'quota', label: $localize`Video quota` },
|
{ id: 'quota', label: $localize`Video quota` },
|
||||||
{ id: 'createdAt', label: $localize`Created` }
|
{ id: 'createdAt', label: $localize`Created` },
|
||||||
|
{ id: 'lastLoginDate', label: $localize`Last login` },
|
||||||
|
|
||||||
|
{ id: 'quotaDaily', label: $localize`Daily quota` },
|
||||||
|
{ id: 'pluginAuth', label: $localize`Auth plugin` }
|
||||||
]
|
]
|
||||||
|
|
||||||
this.selectedColumns = this.columns.map(c => c.id)
|
this.loadSelectedColumns()
|
||||||
|
}
|
||||||
|
|
||||||
this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` })
|
loadSelectedColumns () {
|
||||||
this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` })
|
const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY)
|
||||||
this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` })
|
|
||||||
|
if (result) {
|
||||||
|
try {
|
||||||
|
this.selectedColumns = JSON.parse(result)
|
||||||
|
return
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Cannot load selected columns.', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behaviour
|
||||||
|
this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSelectedColumns () {
|
||||||
|
this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdentifier () {
|
getIdentifier () {
|
||||||
|
|
|
@ -39,6 +39,10 @@ export abstract class RestTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveSort () {
|
||||||
|
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
|
||||||
|
}
|
||||||
|
|
||||||
loadLazy (event: LazyLoadEvent) {
|
loadLazy (event: LazyLoadEvent) {
|
||||||
logger('Load lazy %o.', event)
|
logger('Load lazy %o.', event)
|
||||||
|
|
||||||
|
@ -60,10 +64,6 @@ export abstract class RestTable {
|
||||||
this.saveSort()
|
this.saveSort()
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSort () {
|
|
||||||
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch (search: string) {
|
onSearch (search: string) {
|
||||||
this.search = search
|
this.search = search
|
||||||
this.reloadData()
|
this.reloadData()
|
||||||
|
|
|
@ -32,7 +32,7 @@ import {
|
||||||
usersListValidator,
|
usersListValidator,
|
||||||
usersRegisterValidator,
|
usersRegisterValidator,
|
||||||
usersRemoveValidator,
|
usersRemoveValidator,
|
||||||
usersSortValidator,
|
adminUsersSortValidator,
|
||||||
usersUpdateValidator
|
usersUpdateValidator
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import {
|
import {
|
||||||
|
@ -84,7 +84,7 @@ usersRouter.get('/',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureUserHasRight(UserRight.MANAGE_USERS),
|
ensureUserHasRight(UserRight.MANAGE_USERS),
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
usersSortValidator,
|
adminUsersSortValidator,
|
||||||
setDefaultSort,
|
setDefaultSort,
|
||||||
setDefaultPagination,
|
setDefaultPagination,
|
||||||
usersListValidator,
|
usersListValidator,
|
||||||
|
@ -277,7 +277,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listUsers (req: express.Request, res: express.Response) {
|
async function listUsers (req: express.Request, res: express.Response) {
|
||||||
const resultList = await UserModel.listForApi({
|
const resultList = await UserModel.listForAdminApi({
|
||||||
start: req.query.start,
|
start: req.query.start,
|
||||||
count: req.query.count,
|
count: req.query.count,
|
||||||
sort: req.query.sort,
|
sort: req.query.sort,
|
||||||
|
|
|
@ -58,7 +58,7 @@ const WEBSERVER = {
|
||||||
|
|
||||||
// Sortable columns per schema
|
// Sortable columns per schema
|
||||||
const SORTABLE_COLUMNS = {
|
const SORTABLE_COLUMNS = {
|
||||||
USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ],
|
ADMIN_USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ],
|
||||||
USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ],
|
USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ],
|
||||||
ACCOUNTS: [ 'createdAt' ],
|
ACCOUNTS: [ 'createdAt' ],
|
||||||
JOBS: [ 'createdAt' ],
|
JOBS: [ 'createdAt' ],
|
||||||
|
|
|
@ -28,7 +28,7 @@ function createSortableColumns (sortableColumns: string[]) {
|
||||||
return sortableColumns.concat(sortableColumnDesc)
|
return sortableColumns.concat(sortableColumnDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS)
|
const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS)
|
||||||
const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
|
const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
|
||||||
const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
|
const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
|
||||||
const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
|
const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
|
||||||
|
@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
usersSortValidator,
|
adminUsersSortValidator,
|
||||||
abusesSortValidator,
|
abusesSortValidator,
|
||||||
videoChannelsSortValidator,
|
videoChannelsSortValidator,
|
||||||
videoImportsSortValidator,
|
videoImportsSortValidator,
|
||||||
|
|
|
@ -66,7 +66,7 @@ import { ActorModel } from '../actor/actor'
|
||||||
import { ActorFollowModel } from '../actor/actor-follow'
|
import { ActorFollowModel } from '../actor/actor-follow'
|
||||||
import { ActorImageModel } from '../actor/actor-image'
|
import { ActorImageModel } from '../actor/actor-image'
|
||||||
import { OAuthTokenModel } from '../oauth/oauth-token'
|
import { OAuthTokenModel } from '../oauth/oauth-token'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getAdminUsersSort, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { VideoImportModel } from '../video/video-import'
|
import { VideoImportModel } from '../video/video-import'
|
||||||
|
@ -461,7 +461,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
|
||||||
return this.count()
|
return this.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
static listForApi (parameters: {
|
static listForAdminApi (parameters: {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
@ -497,7 +497,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
|
||||||
const query: FindOptions = {
|
const query: FindOptions = {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
order: getSort(sort),
|
order: getAdminUsersSort(sort),
|
||||||
where
|
where
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt
|
||||||
|
|
||||||
if (field.toLowerCase() === 'match') { // Search
|
if (field.toLowerCase() === 'match') { // Search
|
||||||
finalField = Sequelize.col('similarity')
|
finalField = Sequelize.col('similarity')
|
||||||
} else if (field === 'videoQuotaUsed') { // Users list
|
|
||||||
finalField = Sequelize.col('videoQuotaUsed')
|
|
||||||
} else {
|
} else {
|
||||||
finalField = field
|
finalField = field
|
||||||
}
|
}
|
||||||
|
@ -20,6 +18,25 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt
|
||||||
return [ [ finalField, direction ], lastSort ]
|
return [ [ finalField, direction ], lastSort ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAdminUsersSort (value: string): OrderItem[] {
|
||||||
|
const { direction, field } = buildDirectionAndField(value)
|
||||||
|
|
||||||
|
let finalField: string | ReturnType<typeof Sequelize.col>
|
||||||
|
|
||||||
|
if (field === 'videoQuotaUsed') { // Users list
|
||||||
|
finalField = Sequelize.col('videoQuotaUsed')
|
||||||
|
} else {
|
||||||
|
finalField = field
|
||||||
|
}
|
||||||
|
|
||||||
|
const nullPolicy = direction === 'ASC'
|
||||||
|
? 'NULLS FIRST'
|
||||||
|
: 'NULLS LAST'
|
||||||
|
|
||||||
|
// FIXME: typings
|
||||||
|
return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ]
|
||||||
|
}
|
||||||
|
|
||||||
function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
const { direction, field } = buildDirectionAndField(value)
|
||||||
|
|
||||||
|
@ -260,6 +277,7 @@ export {
|
||||||
buildLocalAccountIdsIn,
|
buildLocalAccountIdsIn,
|
||||||
getSort,
|
getSort,
|
||||||
getCommentSort,
|
getCommentSort,
|
||||||
|
getAdminUsersSort,
|
||||||
getVideoSort,
|
getVideoSort,
|
||||||
getBlacklistSort,
|
getBlacklistSort,
|
||||||
createSimilarityAttribute,
|
createSimilarityAttribute,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user