Skip to content

Commit

Permalink
Improve admin users list table
Browse files Browse the repository at this point in the history
 * Fix last login sort with null values
 * Remember last selected columns
 * Display last login date by default
  • Loading branch information
Chocobozzz committed May 24, 2022
1 parent 3eba7ab commit 87a0cac
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 25 deletions.
Expand Up @@ -5,7 +5,7 @@ <h1>

<p-table
[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"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
Expand Down
@@ -1,7 +1,7 @@
import { SortMeta } from 'primeng/api'
import { Component, OnInit, ViewChild } from '@angular/core'
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 { AdvancedInputFilter } from '@app/shared/shared-forms'
import { Actor, DropdownAction } from '@app/shared/shared-main'
Expand All @@ -22,6 +22,8 @@ type UserForList = User & {
styleUrls: [ './user-list.component.scss' ]
})
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

users: (User & { accountMutedStatus: AccountMutedStatus })[] = []
Expand Down Expand Up @@ -56,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit {

requiresEmailVerification = false

private _selectedColumns: string[]
private _selectedColumns: string[] = []

constructor (
protected route: ActivatedRoute,
Expand All @@ -66,7 +68,8 @@ export class UserListComponent extends RestTable implements OnInit {
private serverService: ServerService,
private auth: AuthService,
private blocklist: BlocklistService,
private userAdminService: UserAdminService
private userAdminService: UserAdminService,
private peertubeLocalStorage: LocalStorageService
) {
super()
}
Expand All @@ -76,11 +79,13 @@ export class UserListComponent extends RestTable implements OnInit {
}

get selectedColumns () {
return this._selectedColumns
return this._selectedColumns || []
}

set selectedColumns (val: string[]) {
this._selectedColumns = val

this.saveSelectedColumns()
}

ngOnInit () {
Expand Down Expand Up @@ -126,14 +131,35 @@ export class UserListComponent extends RestTable implements OnInit {
{ id: 'role', label: $localize`Role` },
{ id: 'email', label: $localize`Email` },
{ 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()
}

loadSelectedColumns () {
const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY)

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
}

this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` })
this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` })
this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` })
saveSelectedColumns () {
this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
}

getIdentifier () {
Expand Down
8 changes: 4 additions & 4 deletions client/src/app/core/rest/rest-table.ts
Expand Up @@ -39,6 +39,10 @@ export abstract class RestTable {
}
}

saveSort () {
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
}

loadLazy (event: LazyLoadEvent) {
logger('Load lazy %o.', event)

Expand All @@ -60,10 +64,6 @@ export abstract class RestTable {
this.saveSort()
}

saveSort () {
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
}

onSearch (search: string) {
this.search = search
this.reloadData()
Expand Down
6 changes: 3 additions & 3 deletions server/controllers/api/users/index.ts
Expand Up @@ -32,7 +32,7 @@ import {
usersListValidator,
usersRegisterValidator,
usersRemoveValidator,
usersSortValidator,
adminUsersSortValidator,
usersUpdateValidator
} from '../../../middlewares'
import {
Expand Down Expand Up @@ -84,7 +84,7 @@ usersRouter.get('/',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
paginationValidator,
usersSortValidator,
adminUsersSortValidator,
setDefaultSort,
setDefaultPagination,
usersListValidator,
Expand Down Expand Up @@ -277,7 +277,7 @@ async function autocompleteUsers (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,
count: req.query.count,
sort: req.query.sort,
Expand Down
2 changes: 1 addition & 1 deletion server/initializers/constants.ts
Expand Up @@ -58,7 +58,7 @@ const WEBSERVER = {

// Sortable columns per schema
const SORTABLE_COLUMNS = {
USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ],
ADMIN_USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ],
USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ],
ACCOUNTS: [ 'createdAt' ],
JOBS: [ 'createdAt' ],
Expand Down
4 changes: 2 additions & 2 deletions server/middlewares/validators/sort.ts
Expand Up @@ -28,7 +28,7 @@ function createSortableColumns (sortableColumns: string[]) {
return sortableColumns.concat(sortableColumnDesc)
}

const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS)
const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS)
const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
Expand Down Expand Up @@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH
// ---------------------------------------------------------------------------

export {
usersSortValidator,
adminUsersSortValidator,
abusesSortValidator,
videoChannelsSortValidator,
videoImportsSortValidator,
Expand Down
6 changes: 3 additions & 3 deletions server/models/user/user.ts
Expand Up @@ -66,7 +66,7 @@ import { ActorModel } from '../actor/actor'
import { ActorFollowModel } from '../actor/actor-follow'
import { ActorImageModel } from '../actor/actor-image'
import { OAuthTokenModel } from '../oauth/oauth-token'
import { getSort, throwIfNotValid } from '../utils'
import { getAdminUsersSort, throwIfNotValid } from '../utils'
import { VideoModel } from '../video/video'
import { VideoChannelModel } from '../video/video-channel'
import { VideoImportModel } from '../video/video-import'
Expand Down Expand Up @@ -461,7 +461,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
return this.count()
}

static listForApi (parameters: {
static listForAdminApi (parameters: {
start: number
count: number
sort: string
Expand Down Expand Up @@ -497,7 +497,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
const query: FindOptions = {
offset: start,
limit: count,
order: getSort(sort),
order: getAdminUsersSort(sort),
where
}

Expand Down
22 changes: 20 additions & 2 deletions server/models/utils.ts
Expand Up @@ -11,15 +11,32 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt

if (field.toLowerCase() === 'match') { // Search
finalField = Sequelize.col('similarity')
} else if (field === 'videoQuotaUsed') { // Users list
finalField = Sequelize.col('videoQuotaUsed')
} else {
finalField = field
}

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[] {
const { direction, field } = buildDirectionAndField(value)

Expand Down Expand Up @@ -260,6 +277,7 @@ export {
buildLocalAccountIdsIn,
getSort,
getCommentSort,
getAdminUsersSort,
getVideoSort,
getBlacklistSort,
createSimilarityAttribute,
Expand Down

0 comments on commit 87a0cac

Please sign in to comment.