From 65b21c961c69c4a63c7c0c34be3d6d034a1176c7 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Oct 2018 16:43:14 +0200 Subject: Add ability to mute a user/instance by server in client --- client/src/app/+accounts/accounts.component.html | 6 +- client/src/app/+admin/admin.module.ts | 3 + .../+admin/moderation/instance-blocklist/index.ts | 2 + .../instance-account-blocklist.component.html | 22 ++++ .../instance-account-blocklist.component.scss | 7 ++ .../instance-account-blocklist.component.ts | 59 +++++++++ .../instance-server-blocklist.component.html | 23 ++++ .../instance-server-blocklist.component.scss | 7 ++ .../instance-server-blocklist.component.ts | 60 +++++++++ .../+admin/moderation/moderation.component.html | 4 + .../app/+admin/moderation/moderation.component.ts | 8 ++ .../src/app/+admin/moderation/moderation.routes.ts | 23 ++++ client/src/app/shared/account/account.model.ts | 12 +- .../app/shared/blocklist/account-block.model.ts | 2 +- .../src/app/shared/blocklist/blocklist.service.ts | 56 +++++++++ client/src/app/shared/blocklist/index.ts | 2 +- .../user-moderation-dropdown.component.ts | 137 ++++++++++++++++++--- client/src/sass/include/_bootstrap-variables.scss | 4 +- 18 files changed, 410 insertions(+), 27 deletions(-) create mode 100644 client/src/app/+admin/moderation/instance-blocklist/index.ts create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss create mode 100644 client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts (limited to 'client/src') diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 60dbcdf1d..c1377c1ea 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html @@ -10,8 +10,10 @@
{{ account.nameWithHost }}
Banned - Muted - Instance muted + Muted + Muted by your instance + Instance muted + Instance muted by your instance + + + + Account + Muted at + + + + + + {{ accountBlock.blockedAccount.nameWithHost }} + {{ accountBlock.createdAt }} + + + + + + diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss new file mode 100644 index 000000000..6028b75ea --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss @@ -0,0 +1,7 @@ +@import '_variables'; +@import '_mixins'; + +.unblock-button { + @include peertube-button; + @include grey-button; +} \ No newline at end of file diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts new file mode 100644 index 000000000..3f243aee4 --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { RestPagination, RestTable } from '@app/shared' +import { SortMeta } from 'primeng/components/common/sortmeta' +import { BlocklistService, AccountBlock } from '@app/shared/blocklist' + +@Component({ + selector: 'my-instance-account-blocklist', + styleUrls: [ './instance-account-blocklist.component.scss' ], + templateUrl: './instance-account-blocklist.component.html' +}) +export class InstanceAccountBlocklistComponent extends RestTable implements OnInit { + blockedAccounts: AccountBlock[] = [] + totalRecords = 0 + rowsPerPage = 10 + sort: SortMeta = { field: 'createdAt', order: -1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + constructor ( + private notificationsService: NotificationsService, + private blocklistService: BlocklistService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + this.initialize() + } + + unblockAccount (accountBlock: AccountBlock) { + const blockedAccount = accountBlock.blockedAccount + + this.blocklistService.unblockAccountByInstance(blockedAccount) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost }) + ) + + this.loadData() + } + ) + } + + protected loadData () { + return this.blocklistService.getInstanceAccountBlocklist(this.pagination, this.sort) + .subscribe( + resultList => { + this.blockedAccounts = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } +} diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html new file mode 100644 index 000000000..859c0f916 --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html @@ -0,0 +1,23 @@ + + + + + Instance + Muted at + + + + + + + {{ serverBlock.blockedServer.host }} + {{ serverBlock.createdAt }} + + + + + + diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss new file mode 100644 index 000000000..6028b75ea --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss @@ -0,0 +1,7 @@ +@import '_variables'; +@import '_mixins'; + +.unblock-button { + @include peertube-button; + @include grey-button; +} \ No newline at end of file diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts new file mode 100644 index 000000000..9459117a3 --- /dev/null +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { RestPagination, RestTable } from '@app/shared' +import { SortMeta } from 'primeng/components/common/sortmeta' +import { BlocklistService } from '@app/shared/blocklist' +import { ServerBlock } from '../../../../../../shared' + +@Component({ + selector: 'my-instance-server-blocklist', + styleUrls: [ './instance-server-blocklist.component.scss' ], + templateUrl: './instance-server-blocklist.component.html' +}) +export class InstanceServerBlocklistComponent extends RestTable implements OnInit { + blockedAccounts: ServerBlock[] = [] + totalRecords = 0 + rowsPerPage = 10 + sort: SortMeta = { field: 'createdAt', order: -1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + constructor ( + private notificationsService: NotificationsService, + private blocklistService: BlocklistService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + this.initialize() + } + + unblockServer (serverBlock: ServerBlock) { + const host = serverBlock.blockedServer.host + + this.blocklistService.unblockServerByInstance(host) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Instance {{host}} unmuted by your instance.', { host }) + ) + + this.loadData() + } + ) + } + + protected loadData () { + return this.blocklistService.getInstanceServerBlocklist(this.pagination, this.sort) + .subscribe( + resultList => { + this.blockedAccounts = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } +} diff --git a/client/src/app/+admin/moderation/moderation.component.html b/client/src/app/+admin/moderation/moderation.component.html index 91e87fcd4..8ec7278ef 100644 --- a/client/src/app/+admin/moderation/moderation.component.html +++ b/client/src/app/+admin/moderation/moderation.component.html @@ -5,6 +5,10 @@ Video abuses Blacklisted videos + + Muted accounts + + Muted servers diff --git a/client/src/app/+admin/moderation/moderation.component.ts b/client/src/app/+admin/moderation/moderation.component.ts index 0f4efb970..7f85f920e 100644 --- a/client/src/app/+admin/moderation/moderation.component.ts +++ b/client/src/app/+admin/moderation/moderation.component.ts @@ -16,4 +16,12 @@ export class ModerationComponent { hasVideoBlacklistRight () { return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) } + + hasAccountsBlacklistRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST) + } + + hasServersBlacklistRight () { + return this.auth.getUser().hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST) + } } diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index 6d81b9b36..bc6dd49d5 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts @@ -4,6 +4,7 @@ import { UserRightGuard } from '@app/core' import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list' import { VideoBlacklistListComponent } from '@app/+admin/moderation/video-blacklist-list' import { ModerationComponent } from '@app/+admin/moderation/moderation.component' +import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' export const ModerationRoutes: Routes = [ { @@ -46,6 +47,28 @@ export const ModerationRoutes: Routes = [ title: 'Blacklisted videos' } } + }, + { + path: 'blocklist/accounts', + component: InstanceAccountBlocklistComponent, + canActivate: [ UserRightGuard ], + data: { + userRight: UserRight.MANAGE_ACCOUNTS_BLOCKLIST, + meta: { + title: 'Muted accounts' + } + } + }, + { + path: 'blocklist/servers', + component: InstanceServerBlocklistComponent, + canActivate: [ UserRightGuard ], + data: { + userRight: UserRight.MANAGE_SERVER_REDUNDANCY, + meta: { + title: 'Muted instances' + } + } } ] } diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts index 0aba9428a..c5cd2051c 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/account/account.model.ts @@ -5,8 +5,10 @@ export class Account extends Actor implements ServerAccount { displayName: string description: string nameWithHost: string - muted: boolean - mutedServer: boolean + mutedByUser: boolean + mutedByInstance: boolean + mutedServerByUser: boolean + mutedServerByInstance: boolean userId?: number @@ -18,7 +20,9 @@ export class Account extends Actor implements ServerAccount { this.userId = hash.userId this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) - this.muted = false - this.mutedServer = false + this.mutedByUser = false + this.mutedByInstance = false + this.mutedServerByUser = false + this.mutedServerByInstance = false } } diff --git a/client/src/app/shared/blocklist/account-block.model.ts b/client/src/app/shared/blocklist/account-block.model.ts index 336680f65..e7b433d88 100644 --- a/client/src/app/shared/blocklist/account-block.model.ts +++ b/client/src/app/shared/blocklist/account-block.model.ts @@ -11,4 +11,4 @@ export class AccountBlock implements AccountBlockServer { this.blockedAccount = new Account(block.blockedAccount) this.createdAt = block.createdAt } -} \ No newline at end of file +} diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/blocklist/blocklist.service.ts index d9c318258..c1f7312f0 100644 --- a/client/src/app/shared/blocklist/blocklist.service.ts +++ b/client/src/app/shared/blocklist/blocklist.service.ts @@ -11,6 +11,7 @@ import { AccountBlock } from '@app/shared/blocklist/account-block.model' @Injectable() export class BlocklistService { static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist' + static BASE_SERVER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/server/blocklist' constructor ( private authHttp: HttpClient, @@ -73,6 +74,61 @@ export class BlocklistService { .pipe(catchError(err => this.restExtractor.handleError(err))) } + /*********************** Instance -> Account blocklist ***********************/ + + getInstanceAccountBlocklist (pagination: RestPagination, sort: SortMeta) { + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', { params }) + .pipe( + map(res => this.restExtractor.convertResultListDateToHuman(res)), + map(res => this.restExtractor.applyToResultListData(res, this.formatAccountBlock.bind(this))), + catchError(err => this.restExtractor.handleError(err)) + ) + } + + blockAccountByInstance (account: Account) { + const body = { accountName: account.nameWithHost } + + return this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', body) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + unblockAccountByInstance (account: Account) { + const path = BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost + + return this.authHttp.delete(path) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + /*********************** Instance -> Server blocklist ***********************/ + + getInstanceServerBlocklist (pagination: RestPagination, sort: SortMeta) { + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/servers', { params }) + .pipe( + map(res => this.restExtractor.convertResultListDateToHuman(res)), + catchError(err => this.restExtractor.handleError(err)) + ) + } + + blockServerByInstance (host: string) { + const body = { host } + + return this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/servers', body) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + unblockServerByInstance (host: string) { + const path = BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/servers/' + host + + return this.authHttp.delete(path) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + private formatAccountBlock (accountBlock: AccountBlockServer) { return new AccountBlock(accountBlock) } diff --git a/client/src/app/shared/blocklist/index.ts b/client/src/app/shared/blocklist/index.ts index 8cf6a55f7..5886ca07e 100644 --- a/client/src/app/shared/blocklist/index.ts +++ b/client/src/app/shared/blocklist/index.ts @@ -1,2 +1,2 @@ export * from './blocklist.service' -export * from './account-block.model' \ No newline at end of file +export * from './account-block.model' diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index 2f4a55f37..908f0b8e0 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts @@ -26,7 +26,7 @@ export class UserModerationDropdownComponent implements OnChanges { @Output() userChanged = new EventEmitter() @Output() userDeleted = new EventEmitter() - userActions: DropdownAction[] = [] + userActions: DropdownAction<{ user: User, account: Account }>[] = [] constructor ( private authService: AuthService, @@ -106,7 +106,7 @@ export class UserModerationDropdownComponent implements OnChanges { this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost }) ) - this.account.muted = true + this.account.mutedByUser = true this.userChanged.emit() }, @@ -123,7 +123,7 @@ export class UserModerationDropdownComponent implements OnChanges { this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost }) ) - this.account.muted = false + this.account.mutedByUser = false this.userChanged.emit() }, @@ -140,7 +140,7 @@ export class UserModerationDropdownComponent implements OnChanges { this.i18n('Instance {{host}} muted.', { host }) ) - this.account.mutedServer = true + this.account.mutedServerByUser = true this.userChanged.emit() }, @@ -157,7 +157,75 @@ export class UserModerationDropdownComponent implements OnChanges { this.i18n('Instance {{host}} unmuted.', { host }) ) - this.account.mutedServer = false + this.account.mutedServerByUser = false + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + blockAccountByInstance (account: Account) { + this.blocklistService.blockAccountByInstance(account) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }) + ) + + this.account.mutedByInstance = true + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + unblockAccountByInstance (account: Account) { + this.blocklistService.unblockAccountByInstance(account) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Account {{nameWithHost}} unmuted by the instance.', { nameWithHost: account.nameWithHost }) + ) + + this.account.mutedByInstance = false + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + blockServerByInstance (host: string) { + this.blocklistService.blockServerByInstance(host) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Instance {{host}} muted by the instance.', { host }) + ) + + this.account.mutedServerByInstance = true + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + unblockServerByInstance (host: string) { + this.blocklistService.unblockServerByInstance(host) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Instance {{host}} unmuted by the instance.', { host }) + ) + + this.account.mutedServerByInstance = false this.userChanged.emit() }, @@ -189,41 +257,74 @@ export class UserModerationDropdownComponent implements OnChanges { }, { label: this.i18n('Ban'), - handler: ({ user }) => this.openBanUserModal(user), - isDisplayed: ({ user }) => !user.muted + handler: ({ user }: { user: User }) => this.openBanUserModal(user), + isDisplayed: ({ user }: { user: User }) => !user.blocked }, { label: this.i18n('Unban'), - handler: ({ user }) => this.unbanUser(user), - isDisplayed: ({ user }) => user.muted + handler: ({ user }: { user: User }) => this.unbanUser(user), + isDisplayed: ({ user }: { user: User }) => user.blocked } ]) } - // User actions on accounts/servers + // Actions on accounts/servers if (this.account) { + // User actions this.userActions = this.userActions.concat([ { label: this.i18n('Mute this account'), - isDisplayed: ({ account }) => account.muted === false, - handler: ({ account }) => this.blockAccountByUser(account) + isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === false, + handler: ({ account }: { account: Account }) => this.blockAccountByUser(account) }, { label: this.i18n('Unmute this account'), - isDisplayed: ({ account }) => account.muted === true, - handler: ({ account }) => this.unblockAccountByUser(account) + isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === true, + handler: ({ account }: { account: Account }) => this.unblockAccountByUser(account) }, { label: this.i18n('Mute the instance'), - isDisplayed: ({ account }) => !account.userId && account.mutedServer === false, - handler: ({ account }) => this.blockServerByUser(account.host) + isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false, + handler: ({ account }: { account: Account }) => this.blockServerByUser(account.host) }, { label: this.i18n('Unmute the instance'), - isDisplayed: ({ account }) => !account.userId && account.mutedServer === true, - handler: ({ account }) => this.unblockServerByUser(account.host) + isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true, + handler: ({ account }: { account: Account }) => this.unblockServerByUser(account.host) } ]) + + // Instance actions + if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { + this.userActions = this.userActions.concat([ + { + label: this.i18n('Mute this account by your instance'), + isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === false, + handler: ({ account }: { account: Account }) => this.blockAccountByInstance(account) + }, + { + label: this.i18n('Unmute this account by your instance'), + isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === true, + handler: ({ account }: { account: Account }) => this.unblockAccountByInstance(account) + } + ]) + } + + // Instance actions + if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { + this.userActions = this.userActions.concat([ + { + label: this.i18n('Mute the instance by your instance'), + isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false, + handler: ({ account }: { account: Account }) => this.blockServerByInstance(account.host) + }, + { + label: this.i18n('Unmute the instance by your instance'), + isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true, + handler: ({ account }: { account: Account }) => this.unblockServerByInstance(account.host) + } + ]) + } } } } diff --git a/client/src/sass/include/_bootstrap-variables.scss b/client/src/sass/include/_bootstrap-variables.scss index ce2532af5..77a20cfe1 100644 --- a/client/src/sass/include/_bootstrap-variables.scss +++ b/client/src/sass/include/_bootstrap-variables.scss @@ -29,4 +29,6 @@ $input-btn-focus-color: inherit; $input-focus-border-color: #ced4da; $nav-pills-link-active-bg: #F0F0F0; -$nav-pills-link-active-color: #000; \ No newline at end of file +$nav-pills-link-active-color: #000; + +$zindex-dropdown: 10000; \ No newline at end of file -- cgit v1.2.3