From 87a0cac618c8ed4a09408273d0f5a468530e8062 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 24 May 2022 15:05:39 +0200 Subject: [PATCH] Improve admin users list table * Fix last login sort with null values * Remember last selected columns * Display last login date by default --- .../users/user-list/user-list.component.html | 2 +- .../users/user-list/user-list.component.ts | 44 +++++++++++++++---- client/src/app/core/rest/rest-table.ts | 8 ++-- server/controllers/api/users/index.ts | 6 +-- server/initializers/constants.ts | 2 +- server/middlewares/validators/sort.ts | 4 +- server/models/user/user.ts | 6 +-- server/models/utils.ts | 22 +++++++++- 8 files changed, 69 insertions(+), 25 deletions(-) diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 30d10e3cf..62eeef8fe 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html @@ -5,7 +5,7 @@ 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 () { diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts index d8b039187..7b765f7fc 100644 --- a/client/src/app/core/rest/rest-table.ts +++ b/client/src/app/core/rest/rest-table.ts @@ -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) @@ -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() diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 8a06bfe93..e13e31aaf 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -32,7 +32,7 @@ import { usersListValidator, usersRegisterValidator, usersRemoveValidator, - usersSortValidator, + adminUsersSortValidator, usersUpdateValidator } from '../../../middlewares' import { @@ -84,7 +84,7 @@ usersRouter.get('/', authenticate, ensureUserHasRight(UserRight.MANAGE_USERS), paginationValidator, - usersSortValidator, + adminUsersSortValidator, setDefaultSort, setDefaultPagination, usersListValidator, @@ -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, diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 02c1ef251..231b86821 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -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' ], diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 3ba668460..c9978e3b4 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts @@ -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) @@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH // --------------------------------------------------------------------------- export { - usersSortValidator, + adminUsersSortValidator, abusesSortValidator, videoChannelsSortValidator, videoImportsSortValidator, diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 326b2e789..20c2222a7 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts @@ -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' @@ -461,7 +461,7 @@ export class UserModel extends Model>> { return this.count() } - static listForApi (parameters: { + static listForAdminApi (parameters: { start: number count: number sort: string @@ -497,7 +497,7 @@ export class UserModel extends Model>> { const query: FindOptions = { offset: start, limit: count, - order: getSort(sort), + order: getAdminUsersSort(sort), where } diff --git a/server/models/utils.ts b/server/models/utils.ts index b57290aff..88e31f22e 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -11,8 +11,6 @@ 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 } @@ -20,6 +18,25 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt return [ [ finalField, direction ], lastSort ] } +function getAdminUsersSort (value: string): OrderItem[] { + const { direction, field } = buildDirectionAndField(value) + + let finalField: string | ReturnType + + 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) @@ -260,6 +277,7 @@ export { buildLocalAccountIdsIn, getSort, getCommentSort, + getAdminUsersSort, getVideoSort, getBlacklistSort, createSimilarityAttribute, -- 2.41.0