From 18d00055f4f7e28a9a4cc81d3159072e9beb779d Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 5 Jan 2023 14:03:46 +0100 Subject: [PATCH] Add dropdown menu item to open admin interface for remote domains (#21895) * Allow /admin/instances/:domain to handle IDNs * Add dropdown menu item to open admin interface for remote domains --- app/controllers/admin/instances_controller.rb | 2 +- .../mastodon/components/status_action_bar.js | 17 ++++++++++++----- .../features/account/components/header.js | 14 ++++++++++---- .../features/status/components/action_bar.js | 15 +++++++++++---- app/javascript/mastodon/permissions.js | 7 ++++--- 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 7c44e88b7..519405726 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -49,7 +49,7 @@ module Admin private def set_instance - @instance = Instance.find(params[:id]) + @instance = Instance.find(TagManager.instance.normalize_domain(params[:id]&.strip)) end def set_instances diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index f93e03e14..00fc94358 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -8,7 +8,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me } from '../initial_state'; import classNames from 'classnames'; -import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions'; +import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -38,6 +38,7 @@ const messages = defineMessages({ embed: { id: 'status.embed', defaultMessage: 'Embed' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' }, + admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, hide: { id: 'status.hide', defaultMessage: 'Hide post' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, @@ -232,7 +233,7 @@ class StatusActionBar extends ImmutablePureComponent { render () { const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; - const { signedIn } = this.context.identity; + const { signedIn, permissions } = this.context.identity; const anonymousAccess = !signedIn; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -312,10 +313,16 @@ class StatusActionBar extends ImmutablePureComponent { } } - if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) { menu.push(null); - menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); - menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); + menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + } + if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) { + const domain = account.get('acct').split('@')[1]; + menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` }); + } } } diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index dddbf4dd4..2481e4783 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -15,7 +15,7 @@ import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; import FollowRequestNoteContainer from '../containers/follow_request_note_container'; -import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions'; +import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { Helmet } from 'react-helmet'; const messages = defineMessages({ @@ -53,6 +53,7 @@ const messages = defineMessages({ unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, + admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' }, openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, }); @@ -163,7 +164,7 @@ class Header extends ImmutablePureComponent { render () { const { account, hidden, intl, domain } = this.props; - const { signedIn } = this.context.identity; + const { signedIn, permissions } = this.context.identity; if (!account) { return null; @@ -288,9 +289,14 @@ class Header extends ImmutablePureComponent { } } - if (account.get('id') !== me && (this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + if ((account.get('id') !== me && (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) { menu.push(null); - menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); + } + if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) { + menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` }); + } } const content = { __html: account.get('note_emojified') }; diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index c1242754c..46ee9f6c1 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -7,7 +7,7 @@ import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; import { me } from '../../../initial_state'; import classNames from 'classnames'; -import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions'; +import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -34,6 +34,7 @@ const messages = defineMessages({ embed: { id: 'status.embed', defaultMessage: 'Embed' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, + admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, @@ -243,10 +244,16 @@ class ActionBar extends React.PureComponent { } } - if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) { menu.push(null); - menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); - menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); + menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); + } + if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) { + const domain = account.get('acct').split('@')[1]; + menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` }); + } } } diff --git a/app/javascript/mastodon/permissions.js b/app/javascript/mastodon/permissions.js index 752ddd6c5..9ea149e5f 100644 --- a/app/javascript/mastodon/permissions.js +++ b/app/javascript/mastodon/permissions.js @@ -1,3 +1,4 @@ -export const PERMISSION_INVITE_USERS = 0x0000000000010000; -export const PERMISSION_MANAGE_USERS = 0x0000000000000400; -export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010; +export const PERMISSION_INVITE_USERS = 0x0000000000010000; +export const PERMISSION_MANAGE_USERS = 0x0000000000000400; +export const PERMISSION_MANAGE_FEDERATION = 0x0000000000000020; +export const PERMISSION_MANAGE_REPORTS = 0x0000000000000010;