display federation restrictions
This commit is contained in:
parent
f572cd937e
commit
f134941eb2
|
@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Add support for logging in via an ActivityPub direct message to the instance admin.
|
||||
- Added option to hide edges between instances if there are only mentions in one direction (off by default).
|
||||
- Added note to neighbors tab to make it explicit that blocked instances _may_ appear.
|
||||
- Added note to neighbors tab to make it explicit that blocked instances may appear.
|
||||
- Added federation tab that shows federation restrictions (only available for some Pleroma instances).
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -19,10 +19,11 @@ defmodule Backend.Api do
|
|||
|> Repo.get_by(domain: domain)
|
||||
end
|
||||
|
||||
@spec get_instance_with_peers(String.t()) :: Instance.t() | nil
|
||||
def get_instance_with_peers(domain) do
|
||||
@spec get_instance_with_relationships(String.t()) :: Instance.t() | nil
|
||||
def get_instance_with_relationships(domain) do
|
||||
Instance
|
||||
|> preload(:peers)
|
||||
|> preload(:federation_restrictions)
|
||||
|> Repo.get_by(domain: domain)
|
||||
end
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ defmodule Backend.Crawler.ApiCrawler do
|
|||
|
||||
# {domain_mentioned, count}
|
||||
@type instance_interactions :: %{String.t() => integer}
|
||||
# {domain, type} e.g. {"gab.com", "reject"}
|
||||
@type federation_restriction :: {String.t(), String.t()}
|
||||
|
||||
@type instance_type :: :mastodon | :pleroma | :gab | :misskey | :gnusocial
|
||||
|
||||
|
@ -27,7 +29,7 @@ defmodule Backend.Crawler.ApiCrawler do
|
|||
:interactions,
|
||||
:statuses_seen,
|
||||
:instance_type,
|
||||
:blocked_domains
|
||||
:federation_restrictions
|
||||
]
|
||||
|
||||
@type t() :: %__MODULE__{
|
||||
|
@ -39,7 +41,7 @@ defmodule Backend.Crawler.ApiCrawler do
|
|||
interactions: instance_interactions,
|
||||
statuses_seen: integer,
|
||||
instance_type: instance_type | nil,
|
||||
blocked_domains: [String.t()]
|
||||
federation_restrictions: [federation_restriction]
|
||||
}
|
||||
|
||||
@empty_result %{
|
||||
|
@ -51,7 +53,7 @@ defmodule Backend.Crawler.ApiCrawler do
|
|||
interactions: %{},
|
||||
statuses_seen: 0,
|
||||
instance_type: nil,
|
||||
blocked_domains: []
|
||||
federation_restrictions: []
|
||||
}
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -227,7 +227,9 @@ defmodule Backend.Crawler do
|
|||
|
||||
new_instances =
|
||||
peers_domains
|
||||
|> list_union(result.blocked_domains)
|
||||
|> list_union(
|
||||
Enum.map(result.federation_restrictions, fn {domain, _restriction_type} -> domain end)
|
||||
)
|
||||
|> Enum.map(&%{domain: &1, inserted_at: now, updated_at: now, next_crawl: now})
|
||||
|
||||
Instance
|
||||
|
@ -282,8 +284,7 @@ defmodule Backend.Crawler do
|
|||
|> Repo.all()
|
||||
|
||||
wanted_restrictions_set =
|
||||
result.blocked_domains
|
||||
|> Enum.map(&{&1, "reject"})
|
||||
result.federation_restrictions
|
||||
|> MapSet.new()
|
||||
|
||||
current_restrictions_set = MapSet.new(current_restrictions)
|
||||
|
|
|
@ -85,16 +85,7 @@ defmodule Backend.Crawler.Crawlers.Nodeinfo do
|
|||
status_count: get_in(nodeinfo, ["usage", "localPosts"]),
|
||||
instance_type: type,
|
||||
version: get_in(nodeinfo, ["software", "version"]),
|
||||
blocked_domains:
|
||||
get_in(nodeinfo, ["metadata", "federation", "mrf_simple", "reject"])
|
||||
|> (fn b ->
|
||||
if b == nil do
|
||||
[]
|
||||
else
|
||||
b
|
||||
end
|
||||
end).()
|
||||
|> Enum.map(&clean_domain(&1))
|
||||
federation_restrictions: get_federation_restrictions(nodeinfo)
|
||||
}
|
||||
)
|
||||
else
|
||||
|
@ -112,4 +103,37 @@ defmodule Backend.Crawler.Crawlers.Nodeinfo do
|
|||
version = String.slice(schema_url, (String.length(schema_url) - 3)..-1)
|
||||
Enum.member?(["1.0", "1.1", "2.0"], version)
|
||||
end
|
||||
|
||||
@spec get_federation_restrictions(any()) :: [ApiCrawler.federation_restriction()]
|
||||
defp get_federation_restrictions(nodeinfo) do
|
||||
mrf_simple = get_in(nodeinfo, ["metadata", "federation", "mrf_simple"])
|
||||
quarantined_domains = get_in(nodeinfo, ["metadata", "federation", "quarantined_instances"])
|
||||
|
||||
quarantined_domains =
|
||||
if quarantined_domains == nil do
|
||||
[]
|
||||
else
|
||||
Enum.map(quarantined_domains, fn domain -> {domain, "quarantine"} end)
|
||||
end
|
||||
|
||||
if mrf_simple != nil do
|
||||
mrf_simple
|
||||
|> Map.take([
|
||||
"report_removal",
|
||||
"reject",
|
||||
"media_removal",
|
||||
"media_nsfw",
|
||||
"federated_timeline_removal",
|
||||
"banner_removal",
|
||||
"avatar_removal",
|
||||
"accept"
|
||||
])
|
||||
|> Enum.flat_map(fn {type, domains} ->
|
||||
Enum.map(domains, fn domain -> {domain, type} end)
|
||||
end)
|
||||
|> Enum.concat(quarantined_domains)
|
||||
else
|
||||
quarantined_domains
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,10 @@ defmodule Backend.Instance do
|
|||
foreign_key: :source_domain,
|
||||
references: :domain
|
||||
|
||||
has_many :federation_restrictions, Backend.FederationRestriction,
|
||||
foreign_key: :source_domain,
|
||||
references: :domain
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule BackendWeb.AdminController do
|
|||
|
||||
# Make sure to update ElasticSearch so that the instance is no longer returned in search results
|
||||
es_instance =
|
||||
Api.get_instance_with_peers(domain)
|
||||
Api.get_instance(domain)
|
||||
|> Map.put(:opt_in, opt_in)
|
||||
|> Map.put(:opt_out, opt_out)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ defmodule BackendWeb.InstanceController do
|
|||
end
|
||||
|
||||
def show(conn, %{"id" => domain}) do
|
||||
instance = Cache.get_instance_with_peers(domain)
|
||||
instance = Cache.get_instance_with_relationships(domain)
|
||||
|
||||
if instance == nil or instance.opt_out == true do
|
||||
send_resp(conn, 404, "Not found")
|
||||
|
|
|
@ -80,6 +80,13 @@ defmodule BackendWeb.InstanceView do
|
|||
instance.peers
|
||||
|> Enum.filter(fn peer -> not peer.opt_out end)
|
||||
|
||||
federation_restrictions =
|
||||
instance.federation_restrictions
|
||||
|> Enum.reduce(%{}, fn %{target_domain: domain, type: type}, acc ->
|
||||
Map.update(acc, type, [domain], fn curr_domains -> [domain | curr_domains] end)
|
||||
end)
|
||||
|> Recase.Enumerable.convert_keys(&Recase.to_camel(&1))
|
||||
|
||||
%{
|
||||
name: instance.domain,
|
||||
description: instance.description,
|
||||
|
@ -89,6 +96,7 @@ defmodule BackendWeb.InstanceView do
|
|||
statusCount: instance.status_count,
|
||||
domainCount: length(instance.peers),
|
||||
peers: render_many(filtered_peers, InstanceView, "peer.json"),
|
||||
federationRestrictions: federation_restrictions,
|
||||
lastUpdated: last_updated,
|
||||
status: "success",
|
||||
type: instance.type,
|
||||
|
|
|
@ -38,17 +38,17 @@ defmodule Graph.Cache do
|
|||
end
|
||||
end
|
||||
|
||||
@spec get_instance_with_peers(String.t()) :: Instance.t()
|
||||
def get_instance_with_peers(domain) do
|
||||
@spec get_instance_with_relationships(String.t()) :: Instance.t()
|
||||
def get_instance_with_relationships(domain) do
|
||||
key = "instance_" <> domain
|
||||
|
||||
case Cache.get(key) do
|
||||
nil ->
|
||||
Appsignal.increment_counter("instance_cache.misses", 1)
|
||||
Logger.debug("Instance cache: miss")
|
||||
instance = Api.get_instance_with_peers(domain)
|
||||
# Cache for one minute
|
||||
Cache.set(key, instance, ttl: 60)
|
||||
instance = Api.get_instance_with_relationships(domain)
|
||||
# Cache for five minutes
|
||||
Cache.set(key, instance, ttl: 300)
|
||||
instance
|
||||
|
||||
data ->
|
||||
|
@ -81,8 +81,8 @@ defmodule Graph.Cache do
|
|||
)
|
||||
|> Repo.one()
|
||||
|
||||
# Cache for one minute
|
||||
Cache.set(key, crawl, ttl: 60)
|
||||
# Cache for five minutes
|
||||
Cache.set(key, crawl, ttl: 300)
|
||||
|
||||
data ->
|
||||
Appsignal.increment_counter("most_recent_crawl_cache.hits", 1)
|
||||
|
|
|
@ -66,7 +66,8 @@ defmodule Backend.MixProject do
|
|||
{:nebulex, "~> 1.1"},
|
||||
{:hunter, "~> 0.5.1"},
|
||||
{:poison, "~> 4.0", override: true},
|
||||
{:scrivener_ecto, "~> 2.2"}
|
||||
{:scrivener_ecto, "~> 2.2"},
|
||||
{:recase, "~> 0.6.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"public_suffix": {:hex, :public_suffix, "0.6.0", "100cfe86f13f9f6f0cf67e743b1b83c78dd1223a2c422fa03ebf4adff514cbc3", [:mix], [{:idna, ">= 1.2.0 and < 6.0.0", [hex: :idna, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||
"recase": {:hex, :recase, "0.6.0", "1dd2dd2f4e06603b74977630e739f08b7fedbb9420cc14de353666c2fc8b99f4", [:mix], [], "hexpm"},
|
||||
"scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm"},
|
||||
"scrivener_ecto": {:hex, :scrivener_ecto, "2.2.0", "53d5f1ba28f35f17891cf526ee102f8f225b7024d1cdaf8984875467158c9c5e", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"shards": {:hex, :shards, "0.6.0", "678d292ad74a4598a872930f9b12251f43e97f6050287f1fb712fbfd3d282f75", [:make, :rebar3], [], "hexpm"},
|
||||
|
|
89
frontend/src/components/organisms/FederationTab.tsx
Normal file
89
frontend/src/components/organisms/FederationTab.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { Classes, H3 } from "@blueprintjs/core";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { IFederationRestrictions } from "../../redux/types";
|
||||
|
||||
const maybeGetList = (domains?: string[]) =>
|
||||
domains && (
|
||||
<ul>
|
||||
{domains.sort().map(domain => (
|
||||
<li key={domain}>
|
||||
<Link to={`/instance/${domain}`} className={`${Classes.BUTTON} ${Classes.MINIMAL}`} role="button">
|
||||
{domain}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
interface IFederationTabProps {
|
||||
restrictions?: IFederationRestrictions;
|
||||
}
|
||||
const FederationTab: React.FC<IFederationTabProps> = ({ restrictions }) => {
|
||||
if (!restrictions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const reportsRemovalList = maybeGetList(restrictions.reportRemoval);
|
||||
const rejectsList = maybeGetList(restrictions.reject);
|
||||
const mediaRemovalsList = maybeGetList(restrictions.mediaRemoval);
|
||||
const mediaNsfwsList = maybeGetList(restrictions.mediaNsfw);
|
||||
const federatedTimelineRemovalsList = maybeGetList(restrictions.federatedTimelineRemoval);
|
||||
const bannerRemovalsList = maybeGetList(restrictions.bannerRemoval);
|
||||
const avatarRemovalsList = maybeGetList(restrictions.avatarRemoval);
|
||||
const acceptedList = maybeGetList(restrictions.accept);
|
||||
|
||||
return (
|
||||
<>
|
||||
{rejectsList && (
|
||||
<>
|
||||
<H3>Blocked instances</H3>
|
||||
{rejectsList}
|
||||
</>
|
||||
)}
|
||||
{reportsRemovalList && (
|
||||
<>
|
||||
<H3>Reports ignored</H3>
|
||||
{reportsRemovalList}
|
||||
</>
|
||||
)}
|
||||
{mediaRemovalsList && (
|
||||
<>
|
||||
<H3>Media removed</H3>
|
||||
{mediaRemovalsList}
|
||||
</>
|
||||
)}
|
||||
{mediaNsfwsList && (
|
||||
<>
|
||||
<H3>Media marked as NSFW</H3>
|
||||
{mediaNsfwsList}
|
||||
</>
|
||||
)}
|
||||
{federatedTimelineRemovalsList && (
|
||||
<>
|
||||
<H3>Hidden from federated timeline</H3>
|
||||
{federatedTimelineRemovalsList}
|
||||
</>
|
||||
)}
|
||||
{bannerRemovalsList && (
|
||||
<>
|
||||
<H3>Banners removed</H3>
|
||||
{bannerRemovalsList}
|
||||
</>
|
||||
)}
|
||||
{avatarRemovalsList && (
|
||||
<>
|
||||
<H3>Avatars removed</H3>
|
||||
{avatarRemovalsList}
|
||||
</>
|
||||
)}
|
||||
{acceptedList && (
|
||||
<>
|
||||
<H3>Whitelisted</H3>
|
||||
{acceptedList}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FederationTab;
|
|
@ -3,3 +3,4 @@ export { default as Nav } from "./Nav";
|
|||
export { default as SidebarContainer } from "./SidebarContainer";
|
||||
export { default as SearchFilters } from "./SearchFilters";
|
||||
export { default as InstanceTable } from "./InstanceTable";
|
||||
export { default as FederationTab } from "./FederationTab";
|
||||
|
|
|
@ -32,6 +32,7 @@ import { IAppState, IGraph, IGraphResponse, IInstanceDetails } from "../../redux
|
|||
import { domainMatchSelector, getFromApi, isSmallScreen } from "../../util";
|
||||
import { InstanceType } from "../atoms";
|
||||
import { Cytoscape, ErrorState } from "../molecules/";
|
||||
import { FederationTab } from "../organisms";
|
||||
|
||||
const InstanceScreenContainer = styled.div`
|
||||
margin-bottom: auto;
|
||||
|
@ -192,6 +193,7 @@ class InstanceScreenImpl extends React.PureComponent<IInstanceScreenProps, IInst
|
|||
|
||||
private renderTabs = () => {
|
||||
const hasNeighbors = this.state.neighbors && this.state.neighbors.length > 0;
|
||||
const federationRestrictions = this.props.instanceDetails && this.props.instanceDetails.federationRestrictions;
|
||||
|
||||
const hasLocalGraph =
|
||||
!!this.state.localGraph && this.state.localGraph.nodes.length > 0 && this.state.localGraph.edges.length > 0;
|
||||
|
@ -212,6 +214,13 @@ class InstanceScreenImpl extends React.PureComponent<IInstanceScreenProps, IInst
|
|||
<Tab id="description" title="Description" panel={this.renderDescription()} />
|
||||
)}
|
||||
{this.shouldRenderStats() && <Tab id="stats" title="Details" panel={this.renderVersionAndCounts()} />}
|
||||
{federationRestrictions && Object.keys(federationRestrictions).length > 0 && (
|
||||
<Tab
|
||||
id="federationRestrictions"
|
||||
title="Federation"
|
||||
panel={<FederationTab restrictions={federationRestrictions} />}
|
||||
/>
|
||||
)}
|
||||
<Tab id="neighbors" title="Neighbors" panel={this.renderNeighbors()} />
|
||||
<Tab id="peers" title="Known peers" panel={this.renderPeers()} />
|
||||
</StyledTabs>
|
||||
|
@ -389,8 +398,8 @@ class InstanceScreenImpl extends React.PureComponent<IInstanceScreenProps, IInst
|
|||
<div>
|
||||
<NeighborsCallout icon={IconNames.INFO_SIGN} title="Warning">
|
||||
<p>
|
||||
Instances that {this.props.instanceName} has blocked may appear on this list. This can happen if users on a
|
||||
blocked instance attempted to mention someone on {this.props.instanceName}.
|
||||
Instances that {this.props.instanceName} has blocked may appear on this list and vice versa. This can happen
|
||||
if users attempt to mention someone on an instance that has blocked them.
|
||||
</p>
|
||||
</NeighborsCallout>
|
||||
<p className={Classes.TEXT_MUTED}>
|
||||
|
|
|
@ -41,6 +41,17 @@ export interface ISearchResultInstance {
|
|||
type?: string;
|
||||
}
|
||||
|
||||
export interface IFederationRestrictions {
|
||||
reportRemoval?: string[];
|
||||
reject?: string[];
|
||||
mediaRemoval?: string[];
|
||||
mediaNsfw?: string[];
|
||||
federatedTimelineRemoval?: string[];
|
||||
bannerRemoval?: string[];
|
||||
avatarRemoval?: string[];
|
||||
accept?: string[];
|
||||
}
|
||||
|
||||
export interface IInstanceDetails {
|
||||
name: string;
|
||||
description?: string;
|
||||
|
@ -50,6 +61,7 @@ export interface IInstanceDetails {
|
|||
statusCount?: number;
|
||||
domainCount?: number;
|
||||
peers?: IPeer[];
|
||||
federationRestrictions: IFederationRestrictions;
|
||||
lastUpdated?: string;
|
||||
status: string;
|
||||
type?: string;
|
||||
|
|
Loading…
Reference in a new issue