user-moderation-dropdown.component.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'
  2. import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
  3. import { Account, DropdownAction } from '@app/shared/shared-main'
  4. import { BulkRemoveCommentsOfBody, User, UserRight } from '@shared/models'
  5. import { UserAdminService } from '../shared-users'
  6. import { BlocklistService } from './blocklist.service'
  7. import { BulkService } from './bulk.service'
  8. import { UserBanModalComponent } from './user-ban-modal.component'
  9. export type AccountMutedStatus =
  10. Pick<Account, 'id' | 'nameWithHost' | 'host' | 'userId' |
  11. 'mutedByInstance' | 'mutedByUser' | 'mutedServerByInstance' | 'mutedServerByUser'>
  12. export type UserModerationDisplayType = {
  13. myAccount?: boolean
  14. instanceAccount?: boolean
  15. instanceUser?: boolean
  16. }
  17. @Component({
  18. selector: 'my-user-moderation-dropdown',
  19. templateUrl: './user-moderation-dropdown.component.html'
  20. })
  21. export class UserModerationDropdownComponent implements OnInit, OnChanges {
  22. @ViewChild('userBanModal') userBanModal: UserBanModalComponent
  23. @Input() user: User
  24. @Input() account: AccountMutedStatus
  25. @Input() prependActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[]
  26. @Input() buttonSize: 'normal' | 'small' = 'normal'
  27. @Input() buttonStyled = true
  28. @Input() placement = 'right-top right-bottom auto'
  29. @Input() label: string
  30. @Input() container: 'body' | undefined = undefined
  31. @Input() displayOptions: UserModerationDisplayType = {
  32. myAccount: true,
  33. instanceAccount: true,
  34. instanceUser: true
  35. }
  36. @Output() userChanged = new EventEmitter()
  37. @Output() userDeleted = new EventEmitter()
  38. userActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[][] = []
  39. requiresEmailVerification = false
  40. constructor (
  41. private authService: AuthService,
  42. private notifier: Notifier,
  43. private confirmService: ConfirmService,
  44. private serverService: ServerService,
  45. private userAdminService: UserAdminService,
  46. private blocklistService: BlocklistService,
  47. private bulkService: BulkService
  48. ) { }
  49. ngOnInit () {
  50. this.serverService.getConfig()
  51. .subscribe(config => this.requiresEmailVerification = config.signup.requiresEmailVerification)
  52. }
  53. ngOnChanges () {
  54. this.buildActions()
  55. }
  56. openBanUserModal (user: User) {
  57. if (user.username === 'root') {
  58. this.notifier.error($localize`You cannot ban root.`)
  59. return
  60. }
  61. this.userBanModal.openModal(user)
  62. }
  63. onUserBanned () {
  64. this.userChanged.emit()
  65. }
  66. async unbanUser (user: User) {
  67. const res = await this.confirmService.confirm($localize`Do you really want to unban ${user.username}?`, $localize`Unban`)
  68. if (res === false) return
  69. this.userAdminService.unbanUsers(user)
  70. .subscribe({
  71. next: () => {
  72. this.notifier.success($localize`User ${user.username} unbanned.`)
  73. this.userChanged.emit()
  74. },
  75. error: err => this.notifier.error(err.message)
  76. })
  77. }
  78. async removeUser (user: User) {
  79. if (user.username === 'root') {
  80. this.notifier.error($localize`You cannot delete root.`)
  81. return
  82. }
  83. // eslint-disable-next-line max-len
  84. const message = $localize`If you remove this user, you won't be able to create another user or channel with <strong>${user.username}</strong> username!`
  85. const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`)
  86. if (res === false) return
  87. this.userAdminService.removeUsers(user)
  88. .subscribe({
  89. next: () => {
  90. this.notifier.success($localize`User ${user.username} deleted.`)
  91. this.userDeleted.emit()
  92. },
  93. error: err => this.notifier.error(err.message)
  94. })
  95. }
  96. setEmailAsVerified (user: User) {
  97. this.userAdminService.updateUser(user.id, { emailVerified: true })
  98. .subscribe({
  99. next: () => {
  100. this.notifier.success($localize`User ${user.username} email set as verified`)
  101. this.userChanged.emit()
  102. },
  103. error: err => this.notifier.error(err.message)
  104. })
  105. }
  106. blockAccountByUser (account: AccountMutedStatus) {
  107. this.blocklistService.blockAccountByUser(account)
  108. .subscribe({
  109. next: () => {
  110. this.notifier.success($localize`Account ${account.nameWithHost} muted.`)
  111. this.account.mutedByUser = true
  112. this.userChanged.emit()
  113. },
  114. error: err => this.notifier.error(err.message)
  115. })
  116. }
  117. unblockAccountByUser (account: AccountMutedStatus) {
  118. this.blocklistService.unblockAccountByUser(account)
  119. .subscribe({
  120. next: () => {
  121. this.notifier.success($localize`Account ${account.nameWithHost} unmuted.`)
  122. this.account.mutedByUser = false
  123. this.userChanged.emit()
  124. },
  125. error: err => this.notifier.error(err.message)
  126. })
  127. }
  128. blockServerByUser (host: string) {
  129. this.blocklistService.blockServerByUser(host)
  130. .subscribe({
  131. next: () => {
  132. this.notifier.success($localize`Instance ${host} muted.`)
  133. this.account.mutedServerByUser = true
  134. this.userChanged.emit()
  135. },
  136. error: err => this.notifier.error(err.message)
  137. })
  138. }
  139. unblockServerByUser (host: string) {
  140. this.blocklistService.unblockServerByUser(host)
  141. .subscribe({
  142. next: () => {
  143. this.notifier.success($localize`Instance ${host} unmuted.`)
  144. this.account.mutedServerByUser = false
  145. this.userChanged.emit()
  146. },
  147. error: err => this.notifier.error(err.message)
  148. })
  149. }
  150. blockAccountByInstance (account: AccountMutedStatus) {
  151. this.blocklistService.blockAccountByInstance(account)
  152. .subscribe({
  153. next: () => {
  154. this.notifier.success($localize`Account ${account.nameWithHost} muted by the instance.`)
  155. this.account.mutedByInstance = true
  156. this.userChanged.emit()
  157. },
  158. error: err => this.notifier.error(err.message)
  159. })
  160. }
  161. unblockAccountByInstance (account: AccountMutedStatus) {
  162. this.blocklistService.unblockAccountByInstance(account)
  163. .subscribe({
  164. next: () => {
  165. this.notifier.success($localize`Account ${account.nameWithHost} unmuted by the instance.`)
  166. this.account.mutedByInstance = false
  167. this.userChanged.emit()
  168. },
  169. error: err => this.notifier.error(err.message)
  170. })
  171. }
  172. blockServerByInstance (host: string) {
  173. this.blocklistService.blockServerByInstance(host)
  174. .subscribe({
  175. next: () => {
  176. this.notifier.success($localize`Instance ${host} muted by the instance.`)
  177. this.account.mutedServerByInstance = true
  178. this.userChanged.emit()
  179. },
  180. error: err => this.notifier.error(err.message)
  181. })
  182. }
  183. unblockServerByInstance (host: string) {
  184. this.blocklistService.unblockServerByInstance(host)
  185. .subscribe({
  186. next: () => {
  187. this.notifier.success($localize`Instance ${host} unmuted by the instance.`)
  188. this.account.mutedServerByInstance = false
  189. this.userChanged.emit()
  190. },
  191. error: err => this.notifier.error(err.message)
  192. })
  193. }
  194. async bulkRemoveCommentsOf (body: BulkRemoveCommentsOfBody) {
  195. const message = $localize`Are you sure you want to remove all the comments of this account?`
  196. const res = await this.confirmService.confirm(message, $localize`Delete account comments`)
  197. if (res === false) return
  198. this.bulkService.removeCommentsOf(body)
  199. .subscribe({
  200. next: () => {
  201. this.notifier.success($localize`Will remove comments of this account (may take several minutes).`)
  202. },
  203. error: err => this.notifier.error(err.message)
  204. })
  205. }
  206. getRouterUserEditLink (user: User) {
  207. return [ '/admin', 'users', 'update', user.id ]
  208. }
  209. private isMyUser (user: User) {
  210. return user && this.authService.getUser().id === user.id
  211. }
  212. private isMyAccount (account: AccountMutedStatus) {
  213. return account && this.authService.getUser().account.id === account.id
  214. }
  215. private buildActions () {
  216. this.userActions = []
  217. if (this.prependActions && this.prependActions.length !== 0) {
  218. this.userActions = [
  219. this.prependActions
  220. ]
  221. }
  222. const myAccountModerationActions = this.buildMyAccountModerationActions()
  223. const instanceModerationActions = this.buildInstanceModerationActions()
  224. if (myAccountModerationActions.length !== 0) this.userActions.push(myAccountModerationActions)
  225. if (instanceModerationActions.length !== 0) this.userActions.push(instanceModerationActions)
  226. }
  227. private buildMyAccountModerationActions () {
  228. if (!this.account || !this.displayOptions.myAccount || !this.authService.isLoggedIn()) return []
  229. const myAccountActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[] = [
  230. {
  231. label: $localize`My account moderation`,
  232. class: [ 'red' ],
  233. isHeader: true
  234. },
  235. {
  236. label: $localize`Mute this account`,
  237. description: $localize`Hide any content from that user from you.`,
  238. isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByUser === false,
  239. handler: ({ account }) => this.blockAccountByUser(account)
  240. },
  241. {
  242. label: $localize`Unmute this account`,
  243. description: $localize`Show back content from that user for you.`,
  244. isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByUser === true,
  245. handler: ({ account }) => this.unblockAccountByUser(account)
  246. },
  247. {
  248. label: $localize`Mute the instance`,
  249. description: $localize`Hide any content from that instance for you.`,
  250. isDisplayed: ({ account }) => !account.userId && account.mutedServerByUser === false,
  251. handler: ({ account }) => this.blockServerByUser(account.host)
  252. },
  253. {
  254. label: $localize`Unmute the instance`,
  255. description: $localize`Show back content from that instance for you.`,
  256. isDisplayed: ({ account }) => !account.userId && account.mutedServerByUser === true,
  257. handler: ({ account }) => this.unblockServerByUser(account.host)
  258. },
  259. {
  260. label: $localize`Remove comments from your videos`,
  261. description: $localize`Remove comments made by this account on your videos.`,
  262. isDisplayed: ({ account }) => !this.isMyAccount(account),
  263. handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' })
  264. }
  265. ]
  266. return myAccountActions
  267. }
  268. private buildInstanceModerationActions () {
  269. if (!this.authService.isLoggedIn()) return []
  270. const authUser = this.authService.getUser()
  271. let instanceActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[] = []
  272. if (this.user && this.displayOptions.instanceUser && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) {
  273. instanceActions = instanceActions.concat([
  274. {
  275. label: $localize`Edit user`,
  276. description: $localize`Change quota, role, and more.`,
  277. linkBuilder: ({ user }) => this.getRouterUserEditLink(user)
  278. },
  279. {
  280. label: $localize`Delete user`,
  281. description: $localize`Videos will be deleted, comments will be tombstoned.`,
  282. isDisplayed: ({ user }) => !this.isMyUser(user),
  283. handler: ({ user }) => this.removeUser(user)
  284. },
  285. {
  286. label: $localize`Ban`,
  287. description: $localize`User won't be able to login anymore, but videos and comments will be kept as is.`,
  288. handler: ({ user }) => this.openBanUserModal(user),
  289. isDisplayed: ({ user }) => !this.isMyUser(user) && !user.blocked
  290. },
  291. {
  292. label: $localize`Unban user`,
  293. description: $localize`Allow the user to login and create videos/comments again`,
  294. handler: ({ user }) => this.unbanUser(user),
  295. isDisplayed: ({ user }) => !this.isMyUser(user) && user.blocked
  296. },
  297. {
  298. label: $localize`Set Email as Verified`,
  299. handler: ({ user }) => this.setEmailAsVerified(user),
  300. isDisplayed: ({ user }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false
  301. }
  302. ])
  303. }
  304. // Instance actions on account blocklists
  305. if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) {
  306. instanceActions = instanceActions.concat([
  307. {
  308. label: $localize`Mute this account`,
  309. description: $localize`Hide any content from that user from you, your instance and its users.`,
  310. isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByInstance === false,
  311. handler: ({ account }) => this.blockAccountByInstance(account)
  312. },
  313. {
  314. label: $localize`Unmute this account`,
  315. description: $localize`Show this user's content to the users of this instance again.`,
  316. isDisplayed: ({ account }) => !this.isMyAccount(account) && account.mutedByInstance === true,
  317. handler: ({ account }) => this.unblockAccountByInstance(account)
  318. }
  319. ])
  320. }
  321. // Instance actions on server blocklists
  322. if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) {
  323. instanceActions = instanceActions.concat([
  324. {
  325. label: $localize`Mute the instance`,
  326. description: $localize`Hide any content from that instance from you, your instance and its users.`,
  327. isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
  328. handler: ({ account }) => this.blockServerByInstance(account.host)
  329. },
  330. {
  331. label: $localize`Unmute the instance by your instance`,
  332. description: $localize`Show back content from that instance for you, your instance and its users.`,
  333. isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
  334. handler: ({ account }) => this.unblockServerByInstance(account.host)
  335. }
  336. ])
  337. }
  338. if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) {
  339. instanceActions = instanceActions.concat([
  340. {
  341. label: $localize`Remove comments from your instance`,
  342. description: $localize`Remove comments made by this account from your instance.`,
  343. isDisplayed: ({ account }) => !this.isMyAccount(account),
  344. handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' })
  345. }
  346. ])
  347. }
  348. if (instanceActions.length === 0) return []
  349. return [ { label: $localize`Instance moderation`, isHeader: true }, ...instanceActions ]
  350. }
  351. }