1
0
Fork 0

Add total video file size column in users list

This commit is contained in:
Chocobozzz 2024-02-21 15:28:38 +01:00
parent db69d9491e
commit 0648d57870
No known key found for this signature in database
GPG key ID: 583A612D890159BE
7 changed files with 56 additions and 12 deletions

View file

@ -68,6 +68,7 @@
<th scope="col" *ngIf="isSelected('email')">{{ getColumn('email').label }}</th> <th scope="col" *ngIf="isSelected('email')">{{ getColumn('email').label }}</th>
<th scope="col" *ngIf="isSelected('quota')" style="width: 160px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th> <th scope="col" *ngIf="isSelected('quota')" style="width: 160px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
<th scope="col" *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th> <th scope="col" *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th>
<th scope="col" *ngIf="isSelected('totalVideoFileSize')" style="width: 100px;">{{ getColumn('totalVideoFileSize').label }}</th>
<th scope="col" *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th> <th scope="col" *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th>
<th scope="col" *ngIf="isSelected('createdAt')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th> <th scope="col" *ngIf="isSelected('createdAt')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
<th scope="col" *ngIf="isSelected('lastLoginDate')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th> <th scope="col" *ngIf="isSelected('lastLoginDate')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
@ -140,6 +141,10 @@
</div> </div>
</td> </td>
<td *ngIf="isSelected('totalVideoFileSize')">
{{ user.totalVideoFileSize | bytes }}
</td>
<td *ngIf="isSelected('pluginAuth')"> <td *ngIf="isSelected('pluginAuth')">
<span *ngIf="user.pluginAuth" [ngbTooltip]="user.pluginAuth">{{ user.pluginAuth }}</span> <span *ngIf="user.pluginAuth" [ngbTooltip]="user.pluginAuth">{{ user.pluginAuth }}</span>
</td> </td>

View file

@ -131,6 +131,7 @@ export class UserListComponent extends RestTable <User> 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: 'totalVideoFileSize', label: $localize`Total size` },
{ id: 'createdAt', label: $localize`Created` }, { id: 'createdAt', label: $localize`Created` },
{ id: 'lastLoginDate', label: $localize`Last login` }, { id: 'lastLoginDate', label: $localize`Last login` },
@ -154,7 +155,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
} }
// Default behaviour // Default behaviour
this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ] this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'totalVideoFileSize', 'createdAt', 'lastLoginDate' ]
return return
} }

View file

@ -37,6 +37,8 @@ export interface User {
videoQuotaUsed?: number videoQuotaUsed?: number
videoQuotaUsedDaily?: number videoQuotaUsedDaily?: number
totalVideoFileSize?: number
videosCount?: number videosCount?: number
abusesCount?: number abusesCount?: number

View file

@ -90,6 +90,8 @@ describe('Test users', function () {
expect(user.email).to.equal('user_1@example.com') expect(user.email).to.equal('user_1@example.com')
expect(user.nsfwPolicy).to.equal('display') expect(user.nsfwPolicy).to.equal('display')
expect(user.totalVideoFileSize).to.equal(0)
const rootUser = data[1] const rootUser = data[1]
expect(rootUser.username).to.equal('root') expect(rootUser.username).to.equal('root')
expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
@ -484,6 +486,7 @@ describe('Test users', function () {
expect(user.abusesCount).to.equal(0) expect(user.abusesCount).to.equal(0)
expect(user.abusesCreatedCount).to.equal(0) expect(user.abusesCreatedCount).to.equal(0)
expect(user.abusesAcceptedCount).to.equal(0) expect(user.abusesAcceptedCount).to.equal(0)
expect(user.totalVideoFileSize).to.equal(0)
}) })
it('Should report correct videos count', async function () { it('Should report correct videos count', async function () {
@ -495,6 +498,7 @@ describe('Test users', function () {
const user = await server.users.get({ userId: user17Id, withStats: true }) const user = await server.users.get({ userId: user17Id, withStats: true })
expect(user.videosCount).to.equal(1) expect(user.videosCount).to.equal(1)
expect(user.totalVideoFileSize).to.not.equal(0)
}) })
it('Should report correct video comments for user', async function () { it('Should report correct video comments for user', async function () {

View file

@ -31,7 +31,7 @@ class LiveQuotaStore {
live.size += size live.size += size
} }
getLiveQuotaOf (userId: number) { getLiveQuotaOfUser (userId: number) {
const currentLives = this.livesPerUser.get(userId) const currentLives = this.livesPerUser.get(userId)
if (!currentLives) return 0 if (!currentLives) return 0

View file

@ -198,14 +198,14 @@ async function sendVerifyRegistrationEmail (registration: MRegistration) {
async function getOriginalVideoFileTotalFromUser (user: MUserId) { async function getOriginalVideoFileTotalFromUser (user: MUserId) {
const base = await UserModel.getUserQuota({ userId: user.id, daily: false }) const base = await UserModel.getUserQuota({ userId: user.id, daily: false })
return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) return base + LiveQuotaStore.Instance.getLiveQuotaOfUser(user.id)
} }
// Returns cumulative size of all video files uploaded in the last 24 hours. // Returns cumulative size of all video files uploaded in the last 24 hours.
async function getOriginalVideoFileTotalDailyFromUser (user: MUserId) { async function getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
const base = await UserModel.getUserQuota({ userId: user.id, daily: true }) const base = await UserModel.getUserQuota({ userId: user.id, daily: true })
return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) return base + LiveQuotaStore.Instance.getLiveQuotaOfUser(user.id)
} }
async function isUserQuotaValid (options: { async function isUserQuotaValid (options: {

View file

@ -83,6 +83,7 @@ enum ScopeNames {
FOR_ME_API = 'FOR_ME_API', FOR_ME_API = 'FOR_ME_API',
WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS', WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS',
WITH_QUOTA = 'WITH_QUOTA', WITH_QUOTA = 'WITH_QUOTA',
WITH_TOTAL_FILE_SIZES = 'WITH_TOTAL_FILE_SIZES',
WITH_STATS = 'WITH_STATS' WITH_STATS = 'WITH_STATS'
} }
@ -168,7 +169,8 @@ enum ScopeNames {
'(' + '(' +
UserModel.generateUserQuotaBaseSQL({ UserModel.generateUserQuotaBaseSQL({
whereUserId: '"UserModel"."id"', whereUserId: '"UserModel"."id"',
daily: false daily: false,
onlyMaxResolution: true
}) + }) +
')' ')'
), ),
@ -179,7 +181,8 @@ enum ScopeNames {
'(' + '(' +
UserModel.generateUserQuotaBaseSQL({ UserModel.generateUserQuotaBaseSQL({
whereUserId: '"UserModel"."id"', whereUserId: '"UserModel"."id"',
daily: true daily: true,
onlyMaxResolution: true
}) + }) +
')' ')'
), ),
@ -188,6 +191,24 @@ enum ScopeNames {
] ]
} }
}, },
[ScopeNames.WITH_TOTAL_FILE_SIZES]: {
attributes: {
include: [
[
literal(
'(' +
UserModel.generateUserQuotaBaseSQL({
whereUserId: '"UserModel"."id"',
daily: false,
onlyMaxResolution: false
}) +
')'
),
'totalVideoFileSize'
]
]
}
},
[ScopeNames.WITH_STATS]: { [ScopeNames.WITH_STATS]: {
attributes: { attributes: {
include: [ include: [
@ -521,7 +542,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
return Promise.all([ return Promise.all([
UserModel.unscoped().count(query), UserModel.unscoped().count(query),
UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query) UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA, ScopeNames.WITH_TOTAL_FILE_SIZES ]).findAll(query)
]).then(([ total, data ]) => ({ total, data })) ]).then(([ total, data ]) => ({ total, data }))
} }
@ -607,6 +628,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
if (withStats) { if (withStats) {
scopes.push(ScopeNames.WITH_QUOTA) scopes.push(ScopeNames.WITH_QUOTA)
scopes.push(ScopeNames.WITH_STATS) scopes.push(ScopeNames.WITH_STATS)
scopes.push(ScopeNames.WITH_TOTAL_FILE_SIZES)
} }
return UserModel.scope(scopes).findByPk(id) return UserModel.scope(scopes).findByPk(id)
@ -805,8 +827,9 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
static generateUserQuotaBaseSQL (options: { static generateUserQuotaBaseSQL (options: {
daily: boolean daily: boolean
whereUserId: '$userId' | '"UserModel"."id"' whereUserId: '$userId' | '"UserModel"."id"'
onlyMaxResolution: boolean
}) { }) {
const { daily, whereUserId } = options const { daily, whereUserId, onlyMaxResolution } = options
const andWhere = daily === true const andWhere = daily === true
? 'AND "video"."createdAt" > now() - interval \'24 hours\'' ? 'AND "video"."createdAt" > now() - interval \'24 hours\''
@ -825,9 +848,13 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' + 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' +
videoChannelJoin videoChannelJoin
const sizeSelect = onlyMaxResolution
? 'MAX("t1"."size")'
: 'SUM("t1"."size")'
return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' +
'FROM (' + 'FROM (' +
`SELECT MAX("t1"."size") AS "size" FROM (${webVideoFiles} UNION ${hlsFiles}) t1 ` + `SELECT ${sizeSelect} AS "size" FROM (${webVideoFiles} UNION ${hlsFiles}) t1 ` +
'GROUP BY "t1"."videoId"' + 'GROUP BY "t1"."videoId"' +
') t2' ') t2'
} }
@ -838,7 +865,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
}) { }) {
const { daily, userId } = options const { daily, userId } = options
const sql = this.generateUserQuotaBaseSQL({ daily, whereUserId: '$userId' }) const sql = this.generateUserQuotaBaseSQL({ daily, whereUserId: '$userId', onlyMaxResolution: true })
const queryOptions = { const queryOptions = {
bind: { userId }, bind: { userId },
@ -914,6 +941,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
const [ abusesCount, abusesAcceptedCount ] = (this.get('abusesCount') as string || ':').split(':') const [ abusesCount, abusesAcceptedCount ] = (this.get('abusesCount') as string || ':').split(':')
const abusesCreatedCount = this.get('abusesCreatedCount') const abusesCreatedCount = this.get('abusesCreatedCount')
const videoCommentsCount = this.get('videoCommentsCount') const videoCommentsCount = this.get('videoCommentsCount')
const totalVideoFileSize = this.get('totalVideoFileSize')
const json: User = { const json: User = {
id: this.id, id: this.id,
@ -943,12 +971,16 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
videoQuota: this.videoQuota, videoQuota: this.videoQuota,
videoQuotaDaily: this.videoQuotaDaily, videoQuotaDaily: this.videoQuotaDaily,
totalVideoFileSize: totalVideoFileSize !== undefined
? forceNumber(totalVideoFileSize)
: undefined,
videoQuotaUsed: videoQuotaUsed !== undefined videoQuotaUsed: videoQuotaUsed !== undefined
? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOfUser(this.id)
: undefined, : undefined,
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOfUser(this.id)
: undefined, : undefined,
videosCount: videosCount !== undefined videosCount: videosCount !== undefined