diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 7a450e4f50c07276a9c44fd6cf5aa4b6110ef0bf..48e5982da3f52b380c4debec0c1cb5043aea5f99 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -62,9 +62,32 @@ class ActorQuerySet(models.QuerySet):
         return qs
 
 
+class DomainQuerySet(models.QuerySet):
+    def external(self):
+        return self.exclude(pk=settings.FEDERATION_HOSTNAME)
+
+    def with_last_activity_date(self):
+        activities = Activity.objects.filter(
+            actor__domain=models.OuterRef("pk")
+        ).order_by("-creation_date")
+
+        return self.annotate(
+            last_activity_date=models.Subquery(activities.values("creation_date")[:1])
+        )
+
+    def with_actors_count(self):
+        return self.annotate(actors_count=models.Count("actors", distinct=True))
+
+    def with_outbox_activities_count(self):
+        return self.annotate(
+            outbox_activities_count=models.Count("actors__outbox_activities")
+        )
+
+
 class Domain(models.Model):
     name = models.CharField(primary_key=True, max_length=255)
     creation_date = models.DateTimeField(default=timezone.now)
+    objects = DomainQuerySet.as_manager()
 
     def __str__(self):
         return self.name
diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py
index 4347b4cc42089a0376577d98dbbf9097e37dd419..d9b9bfc1df0a1e079260095aab74b2c73cb8db75 100644
--- a/api/funkwhale_api/manage/filters.py
+++ b/api/funkwhale_api/manage/filters.py
@@ -1,6 +1,7 @@
 from django_filters import rest_framework as filters
 
 from funkwhale_api.common import fields
+from funkwhale_api.federation import models as federation_models
 from funkwhale_api.music import models as music_models
 from funkwhale_api.users import models as users_models
 
@@ -20,6 +21,14 @@ class ManageUploadFilterSet(filters.FilterSet):
         fields = ["q", "track__album", "track__artist", "track"]
 
 
+class ManageDomainFilterSet(filters.FilterSet):
+    q = fields.SearchFilter(search_fields=["name"])
+
+    class Meta:
+        model = federation_models.Domain
+        fields = ["name"]
+
+
 class ManageUserFilterSet(filters.FilterSet):
     q = fields.SearchFilter(search_fields=["username", "email", "name"])
 
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index 9b5e24f662d9f25c39e980108b119f85e67c8470..8686a99b92abbc788cc63473358727fc66d26d5e 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -3,6 +3,7 @@ from django.db import transaction
 from rest_framework import serializers
 
 from funkwhale_api.common import serializers as common_serializers
+from funkwhale_api.federation import models as federation_models
 from funkwhale_api.music import models as music_models
 from funkwhale_api.users import models as users_models
 
@@ -168,3 +169,28 @@ class ManageInvitationActionSerializer(common_serializers.ActionSerializer):
     @transaction.atomic
     def handle_delete(self, objects):
         return objects.delete()
+
+
+class ManageDomainSerializer(serializers.ModelSerializer):
+    actors_count = serializers.SerializerMethodField()
+    last_activity_date = serializers.SerializerMethodField()
+    outbox_activities_count = serializers.SerializerMethodField()
+
+    class Meta:
+        model = federation_models.Domain
+        fields = [
+            "name",
+            "creation_date",
+            "actors_count",
+            "last_activity_date",
+            "outbox_activities_count",
+        ]
+
+    def get_actors_count(self, o):
+        return getattr(o, "actors_count", 0)
+
+    def get_last_activity_date(self, o):
+        return getattr(o, "last_activity_date", None)
+
+    def get_outbox_activities_count(self, o):
+        return getattr(o, "outbox_activities_count", 0)
diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py
index 9f5503978c435797d012fc0595a8c8b50aa81568..26832f946e5430198a5804030241e9efaa8a6461 100644
--- a/api/funkwhale_api/manage/urls.py
+++ b/api/funkwhale_api/manage/urls.py
@@ -3,6 +3,8 @@ from rest_framework import routers
 
 from . import views
 
+federation_router = routers.SimpleRouter()
+federation_router.register(r"domains", views.ManageDomainViewSet, "domains")
 library_router = routers.SimpleRouter()
 library_router.register(r"uploads", views.ManageUploadViewSet, "uploads")
 users_router = routers.SimpleRouter()
@@ -10,6 +12,10 @@ users_router.register(r"users", views.ManageUserViewSet, "users")
 users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations")
 
 urlpatterns = [
+    url(
+        r"^federation/",
+        include((federation_router.urls, "federation"), namespace="federation"),
+    ),
     url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
     url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
 ]
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index bfd5b2ef21bb3c1854ed711bb52b57f5bdc6a079..30f7179e885e6dbedc7c25bd8d28811405ce1aa5 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -2,6 +2,7 @@ from rest_framework import mixins, response, viewsets
 from rest_framework.decorators import list_route
 
 from funkwhale_api.common import preferences
+from funkwhale_api.federation import models as federation_models
 from funkwhale_api.music import models as music_models
 from funkwhale_api.users import models as users_models
 from funkwhale_api.users.permissions import HasUserPermission
@@ -92,3 +93,26 @@ class ManageInvitationViewSet(
         serializer.is_valid(raise_exception=True)
         result = serializer.save()
         return response.Response(result, status=200)
+
+
+class ManageDomainViewSet(
+    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
+):
+    queryset = (
+        federation_models.Domain.objects.external()
+        .with_last_activity_date()
+        .with_actors_count()
+        .with_outbox_activities_count()
+        .order_by("name")
+    )
+    serializer_class = serializers.ManageDomainSerializer
+    filter_class = filters.ManageDomainFilterSet
+    permission_classes = (HasUserPermission,)
+    required_permissions = ["moderation"]
+    ordering_fields = [
+        "name",
+        "creation_date",
+        "last_activity_date",
+        "actors_count",
+        "outbox_activities_count",
+    ]
diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py
index 18966430e21bd9ccc9ab4637a6178971b0f10804..c3032817c455f0c19062c4ab17d9db89b93d39f2 100644
--- a/api/tests/federation/test_models.py
+++ b/api/tests/federation/test_models.py
@@ -1,6 +1,8 @@
 import pytest
 from django import db
 
+from funkwhale_api.federation import models
+
 
 def test_cannot_duplicate_actor(factories):
     actor = factories["federation.Actor"]()
@@ -67,3 +69,11 @@ def test_actor_get_quota(factories):
 def test_domain_name_saved_properly(value, expected, factories):
     domain = factories["federation.Domain"](name=value)
     assert domain.name == expected
+
+
+def test_external_domains(factories, settings):
+    d1 = factories["federation.Domain"]()
+    d2 = factories["federation.Domain"]()
+    settings.FEDERATION_HOSTNAME = d1.pk
+
+    assert list(models.Domain.objects.external()) == [d2]
diff --git a/api/tests/manage/test_filters.py b/api/tests/manage/test_filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index 2b46ee839c9d53752f8a481f54461a43bfe9214c..be02e6727a2b61a2492c3248da355c95d9907441 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -21,7 +21,7 @@ def test_user_update_permission(factories):
         user,
         data={
             "is_active": False,
-            "permissions": {"federation": False, "upload": True},
+            "permissions": {"moderation": True, "settings": False},
             "upload_quota": 12,
         },
     )
@@ -33,4 +33,21 @@ def test_user_update_permission(factories):
     assert user.upload_quota == 12
     assert user.permission_moderation is True
     assert user.permission_library is False
-    assert user.permission_settings is True
+    assert user.permission_settings is False
+
+
+def test_manage_domain_serializer(factories, now):
+    domain = factories["federation.Domain"]()
+    setattr(domain, "actors_count", 42)
+    setattr(domain, "outbox_activities_count", 23)
+    setattr(domain, "last_activity_date", now)
+    expected = {
+        "name": domain.name,
+        "creation_date": domain.creation_date.isoformat().split("+")[0] + "Z",
+        "last_activity_date": now,
+        "actors_count": 42,
+        "outbox_activities_count": 23,
+    }
+    s = serializers.ManageDomainSerializer(domain)
+
+    assert s.data == expected
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index a9920ce0761b655074cba3a1a7e18104fea8ca49..3d153073a906252273292c0881d4a2583a72aa1d 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -10,6 +10,7 @@ from funkwhale_api.manage import serializers, views
         (views.ManageUploadViewSet, ["library"], "and"),
         (views.ManageUserViewSet, ["settings"], "and"),
         (views.ManageInvitationViewSet, ["settings"], "and"),
+        (views.ManageDomainViewSet, ["moderation"], "and"),
     ],
 )
 def test_permissions(assert_user_permission, view, permissions, operator):
@@ -64,3 +65,15 @@ def test_invitation_view_create(factories, superuser_api_client, mocker):
 
     assert response.status_code == 201
     assert superuser_api_client.user.invitations.latest("id") is not None
+
+
+def test_domain_list(factories, superuser_api_client, settings):
+    factories["federation.Domain"](pk=settings.FEDERATION_HOSTNAME)
+    d = factories["federation.Domain"]()
+    url = reverse("api:v1:manage:federation:domains-list")
+    response = superuser_api_client.get(url)
+
+    assert response.status_code == 200
+
+    assert response.data["count"] == 1
+    assert response.data["results"][0]["name"] == d.pk
diff --git a/api/tests/radios/test_filters.py b/api/tests/radios/test_filters.py
deleted file mode 100644
index 89bb726aff643c7365b759b510f763b738c738d0..0000000000000000000000000000000000000000
--- a/api/tests/radios/test_filters.py
+++ /dev/null
@@ -1,153 +0,0 @@
-import pytest
-from django.core.exceptions import ValidationError
-
-from funkwhale_api.music.models import Track
-from funkwhale_api.radios import filters
-
-
-@filters.registry.register
-class NoopFilter(filters.RadioFilter):
-    code = "noop"
-
-    def get_query(self, candidates, **kwargs):
-        return
-
-
-def test_most_simple_radio_does_not_filter_anything(factories):
-    factories["music.Track"].create_batch(3)
-    radio = factories["radios.Radio"](config=[{"type": "noop"}])
-
-    assert radio.version == 0
-    assert radio.get_candidates().count() == 3
-
-
-def test_filter_can_use_custom_queryset(factories):
-    tracks = factories["music.Track"].create_batch(3)
-    candidates = Track.objects.filter(pk=tracks[0].pk)
-
-    qs = filters.run([{"type": "noop"}], candidates=candidates)
-    assert qs.count() == 1
-    assert qs.first() == tracks[0]
-
-
-def test_filter_on_tag(factories):
-    tracks = factories["music.Track"].create_batch(3, tags=["metal"])
-    factories["music.Track"].create_batch(3, tags=["pop"])
-    expected = tracks
-    f = [{"type": "tag", "names": ["metal"]}]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == expected
-
-
-def test_filter_on_artist(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-    factories["music.Track"].create_batch(3, artist=artist1)
-    factories["music.Track"].create_batch(3, artist=artist2)
-    expected = list(artist1.tracks.order_by("pk"))
-    f = [{"type": "artist", "ids": [artist1.pk]}]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == expected
-
-
-def test_can_combine_with_or(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-    artist3 = factories["music.Artist"]()
-    factories["music.Track"].create_batch(3, artist=artist1)
-    factories["music.Track"].create_batch(3, artist=artist2)
-    factories["music.Track"].create_batch(3, artist=artist3)
-    expected = Track.objects.exclude(artist=artist3).order_by("pk")
-    f = [
-        {"type": "artist", "ids": [artist1.pk]},
-        {"type": "artist", "ids": [artist2.pk], "operator": "or"},
-    ]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == list(expected)
-
-
-def test_can_combine_with_and(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-    metal_tracks = factories["music.Track"].create_batch(
-        2, artist=artist1, tags=["metal"]
-    )
-    factories["music.Track"].create_batch(2, artist=artist1, tags=["pop"])
-    factories["music.Track"].create_batch(3, artist=artist2)
-    expected = metal_tracks
-    f = [
-        {"type": "artist", "ids": [artist1.pk]},
-        {"type": "tag", "names": ["metal"], "operator": "and"},
-    ]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == list(expected)
-
-
-def test_can_negate(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-    factories["music.Track"].create_batch(3, artist=artist1)
-    factories["music.Track"].create_batch(3, artist=artist2)
-    expected = artist2.tracks.order_by("pk")
-    f = [{"type": "artist", "ids": [artist1.pk], "not": True}]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == list(expected)
-
-
-def test_can_group(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-    factories["music.Track"].create_batch(2, artist=artist1)
-    t1 = factories["music.Track"].create_batch(2, artist=artist1, tags=["metal"])
-    factories["music.Track"].create_batch(2, artist=artist2)
-    t2 = factories["music.Track"].create_batch(2, artist=artist2, tags=["metal"])
-    factories["music.Track"].create_batch(2, tags=["metal"])
-    expected = t1 + t2
-    f = [
-        {"type": "tag", "names": ["metal"]},
-        {
-            "type": "group",
-            "operator": "and",
-            "filters": [
-                {"type": "artist", "ids": [artist1.pk], "operator": "or"},
-                {"type": "artist", "ids": [artist2.pk], "operator": "or"},
-            ],
-        },
-    ]
-
-    candidates = filters.run(f)
-    assert list(candidates.order_by("pk")) == list(expected)
-
-
-def test_artist_filter_clean_config(factories):
-    artist1 = factories["music.Artist"]()
-    artist2 = factories["music.Artist"]()
-
-    config = filters.clean_config({"type": "artist", "ids": [artist2.pk, artist1.pk]})
-
-    expected = {
-        "type": "artist",
-        "ids": [artist1.pk, artist2.pk],
-        "names": [artist1.name, artist2.name],
-    }
-    assert filters.clean_config(config) == expected
-
-
-def test_can_check_artist_filter(factories):
-    artist = factories["music.Artist"]()
-
-    assert filters.validate({"type": "artist", "ids": [artist.pk]})
-    with pytest.raises(ValidationError):
-        filters.validate({"type": "artist", "ids": [artist.pk + 1]})
-
-
-def test_can_check_operator():
-    assert filters.validate({"type": "group", "operator": "or", "filters": []})
-    assert filters.validate({"type": "group", "operator": "and", "filters": []})
-    with pytest.raises(ValidationError):
-        assert filters.validate({"type": "group", "operator": "nope", "filters": []})
diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue
index f072ce808fb3dc9cb7e8f0f3f2e3b924bdc2ed73..3a5bf2db8a3ddbee831bbbdf5d88c7fc1d018b3f 100644
--- a/front/src/components/Sidebar.vue
+++ b/front/src/components/Sidebar.vue
@@ -76,19 +76,27 @@
               class="item" :to="{name: 'content.index'}"><i class="upload icon"></i><translate>Add content</translate></router-link>
           </div>
         </div>
-        <div class="item" v-if="$store.state.auth.availablePermissions['settings']">
+        <div class="item" v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']">
           <header class="header"><translate>Administration</translate></header>
           <div class="menu">
             <router-link
+              v-if="$store.state.auth.availablePermissions['settings']"
               class="item"
               :to="{path: '/manage/settings'}">
               <i class="settings icon"></i><translate>Settings</translate>
             </router-link>
             <router-link
+              v-if="$store.state.auth.availablePermissions['settings']"
               class="item"
               :to="{name: 'manage.users.users.list'}">
               <i class="users icon"></i><translate>Users</translate>
             </router-link>
+            <router-link
+              v-if="$store.state.auth.availablePermissions['moderation']"
+              class="item"
+              :to="{name: 'manage.moderation.domains.list'}">
+              <i class="shield icon"></i><translate>Moderation</translate>
+            </router-link>
           </div>
         </div>
       </nav>
diff --git a/front/src/components/common/ActionTable.vue b/front/src/components/common/ActionTable.vue
index e8dec339aaa85d85172af1da44c7c11d8b4dc7b0..5b138a3c6f909d51b39352561f20b8419aae57d7 100644
--- a/front/src/components/common/ActionTable.vue
+++ b/front/src/components/common/ActionTable.vue
@@ -1,7 +1,7 @@
 <template>
   <table class="ui compact very basic single line unstackable table">
     <thead>
-      <tr v-if="actions.length > 0">
+      <tr v-if="actionUrl && actions.length > 0">
         <th colspan="1000">
           <div class="ui small form">
             <div class="ui inline fields">
@@ -130,8 +130,8 @@ import axios from 'axios'
 
 export default {
   props: {
-    actionUrl: {type: String, required: true},
-    idField: {type: String, required: true, default: 'id'},
+    actionUrl: {type: String, required: false, default: null},
+    idField: {type: String, required: false, default: 'id'},
     objectsData: {type: Object, required: true},
     actions: {type: Array, required: true, default: () => { return [] }},
     filters: {type: Object, required: false, default: () => { return {} }},
diff --git a/front/src/components/manage/moderation/DomainsTable.vue b/front/src/components/manage/moderation/DomainsTable.vue
new file mode 100644
index 0000000000000000000000000000000000000000..35cd96d6939dbe0a53509143243865c06aaf6b8b
--- /dev/null
+++ b/front/src/components/manage/moderation/DomainsTable.vue
@@ -0,0 +1,190 @@
+<template>
+  <div>
+    <div class="ui inline form">
+      <div class="fields">
+        <div class="ui field">
+          <label><translate>Search</translate></label>
+          <input type="text" v-model="search" :placeholder="labels.searchPlaceholder" />
+        </div>
+        <div class="field">
+          <label><translate>Ordering</translate></label>
+          <select class="ui dropdown" v-model="ordering">
+            <option v-for="option in orderingOptions" :value="option[0]">
+              {{ sharedLabels.filters[option[1]] }}
+            </option>
+          </select>
+        </div>
+        <div class="field">
+          <label><translate>Ordering direction</translate></label>
+          <select class="ui dropdown" v-model="orderingDirection">
+            <option value="+"><translate>Ascending</translate></option>
+            <option value="-"><translate>Descending</translate></option>
+          </select>
+        </div>
+      </div>
+      </div>
+    <div class="dimmable">
+      <div v-if="isLoading" class="ui active inverted dimmer">
+          <div class="ui loader"></div>
+      </div>
+      <action-table
+        v-if="result"
+        @action-launched="fetchData"
+        :objects-data="result"
+        :actions="actions"
+        :filters="actionFilters">
+        <template slot="header-cells">
+          <th><translate>Name</translate></th>
+          <th><translate>First seen</translate></th>
+          <th><translate>Users</translate></th>
+          <th><translate>Last activity</translate></th>
+          <th><translate>Received messages</translate></th>
+        </template>
+        <template slot="row-cells" slot-scope="scope">
+          <td>
+            <router-link :to="{name: 'manage.moderation.domain.detail', params: {id: scope.obj.name }}">{{ scope.obj.name }}</router-link>
+          </td>
+          <td>
+            <human-date :date="scope.obj.creation_date"></human-date>
+          </td>
+          <td>
+            {{ scope.obj.actors_count }}
+          </td>
+          <td>
+            <human-date v-if="scope.obj.last_activity_date" :date="scope.obj.last_activity_date"></human-date>
+            <translate v-else>N/A</translate>
+          </td>
+          <td>
+            {{ scope.obj.outbox_activities_count }}
+          </td>
+        </template>
+      </action-table>
+    </div>
+    <div>
+      <pagination
+        v-if="result && result.count > paginateBy"
+        @page-changed="selectPage"
+        :compact="true"
+        :current="page"
+        :paginate-by="paginateBy"
+        :total="result.count"
+        ></pagination>
+
+      <span v-if="result && result.results.length > 0">
+        <translate
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+          Showing results %{ start }-%{ end } on %{ total }
+        </translate>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import axios from 'axios'
+import _ from '@/lodash'
+import time from '@/utils/time'
+import Pagination from '@/components/Pagination'
+import ActionTable from '@/components/common/ActionTable'
+import OrderingMixin from '@/components/mixins/Ordering'
+import TranslationsMixin from '@/components/mixins/Translations'
+
+export default {
+  mixins: [OrderingMixin, TranslationsMixin],
+  props: {
+    filters: {type: Object, required: false}
+  },
+  components: {
+    Pagination,
+    ActionTable
+  },
+  data () {
+    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    return {
+      time,
+      isLoading: false,
+      result: null,
+      page: 1,
+      paginateBy: 50,
+      search: '',
+      orderingDirection: defaultOrdering.direction || '+',
+      ordering: defaultOrdering.field,
+      orderingOptions: [
+        ['name', 'name'],
+        ['creation_date', 'first_seen'],
+        ['last_activity_date', 'last_activity'],
+        ['actors_count', 'users'],
+        ['outbox_activities_count', 'received_messages']
+      ]
+
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      let params = _.merge({
+        'page': this.page,
+        'page_size': this.paginateBy,
+        'q': this.search,
+        'ordering': this.getOrderingAsString()
+      }, this.filters)
+      let self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/federation/domains/', {params: params}).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
+  },
+  computed: {
+    labels () {
+      return {
+        searchPlaceholder: this.$gettext('Search by name...')
+      }
+    },
+    actionFilters () {
+      var currentFilters = {
+        q: this.search
+      }
+      if (this.filters) {
+        return _.merge(currentFilters, this.filters)
+      } else {
+        return currentFilters
+      }
+    },
+    actions () {
+      return [
+        // {
+        //   name: 'delete',
+        //   label: this.$gettext('Delete'),
+        //   isDangerous: true
+        // }
+      ]
+    }
+  },
+  watch: {
+    search (newValue) {
+      this.page = 1
+      this.fetchData()
+    },
+    page () {
+      this.fetchData()
+    },
+    ordering () {
+      this.fetchData()
+    },
+    orderingDirection () {
+      this.fetchData()
+    }
+  }
+}
+</script>
diff --git a/front/src/components/mixins/Translations.vue b/front/src/components/mixins/Translations.vue
index be35c2f3432d419123a7432ac976cb7b21d49cc0..c982c9ad7ffedea54bae8664569166ad8e705c09 100644
--- a/front/src/components/mixins/Translations.vue
+++ b/front/src/components/mixins/Translations.vue
@@ -15,6 +15,7 @@ export default {
         },
         filters: {
           creation_date: this.$gettext('Creation date'),
+          first_seen: this.$gettext('First seen date'),
           accessed_date: this.$gettext('Accessed date'),
           modification_date: this.$gettext('Modification date'),
           imported_date: this.$gettext('Imported date'),
@@ -30,6 +31,8 @@ export default {
           date_joined: this.$gettext('Sign-up date'),
           last_activity: this.$gettext('Last activity'),
           username: this.$gettext('Username'),
+          users: this.$gettext('Users'),
+          received_messages: this.$gettext('Received messages'),
         }
       }
     }
diff --git a/front/src/router/index.js b/front/src/router/index.js
index f6b4d309f2f1ef1c8b60518587feb3149d9d6841..55bb4fc8b10400a7daa3079aa9c0008d947d4f2d 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -30,6 +30,8 @@ import AdminUsersBase from '@/views/admin/users/Base'
 import AdminUsersDetail from '@/views/admin/users/UsersDetail'
 import AdminUsersList from '@/views/admin/users/UsersList'
 import AdminInvitationsList from '@/views/admin/users/InvitationsList'
+import AdminModerationBase from '@/views/admin/moderation/Base'
+import AdminDomainsList from '@/views/admin/moderation/DomainsList'
 import ContentBase from '@/views/content/Base'
 import ContentHome from '@/views/content/Home'
 import LibrariesHome from '@/views/content/libraries/Home'
@@ -224,6 +226,17 @@ export default new Router({
         }
       ]
     },
+    {
+      path: '/manage/moderation',
+      component: AdminModerationBase,
+      children: [
+        {
+          path: 'domains',
+          name: 'manage.moderation.domains.list',
+          component: AdminDomainsList
+        }
+      ]
+    },
     {
       path: '/library',
       component: Library,
diff --git a/front/src/store/auth.js b/front/src/store/auth.js
index 70dbe26babe36933a111f286dc257a8236bb2dea..1299dabfe879a76651093384c3e2b55d178e2475 100644
--- a/front/src/store/auth.js
+++ b/front/src/store/auth.js
@@ -9,10 +9,9 @@ export default {
     authenticated: false,
     username: '',
     availablePermissions: {
-      federation: false,
       settings: false,
       library: false,
-      upload: false
+      moderation: false
     },
     profile: null,
     token: '',
diff --git a/front/src/views/admin/moderation/Base.vue b/front/src/views/admin/moderation/Base.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d4487339d3f41bb665e67c7034fdf974f7a72095
--- /dev/null
+++ b/front/src/views/admin/moderation/Base.vue
@@ -0,0 +1,23 @@
+<template>
+  <div class="main pusher"  v-title="labels.manageDomains">
+    <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
+      <router-link
+        class="ui item"
+        :to="{name: 'manage.moderation.domains.list'}"><translate>Domains</translate></router-link>
+    </nav>
+    <router-view :key="$route.fullPath"></router-view>
+  </div>
+</template>
+
+<script>
+export default {
+  computed: {
+    labels() {
+      return {
+        manageDomains: this.$gettext("Manage domains"),
+        secondaryMenu: this.$gettext("Secondary menu")
+      }
+    }
+  }
+}
+</script>
diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..84fb1df4300dffecc15db22048bafc50fb0e27dc
--- /dev/null
+++ b/front/src/views/admin/moderation/DomainsList.vue
@@ -0,0 +1,30 @@
+<template>
+  <main v-title="labels.domains">
+    <section class="ui vertical stripe segment">
+      <h2 class="ui header"><translate>Domains</translate></h2>
+      <div class="ui hidden divider"></div>
+      <domains-table></domains-table>
+    </section>
+  </main>
+</template>
+
+<script>
+import DomainsTable from "@/components/manage/moderation/DomainsTable"
+
+export default {
+  components: {
+    DomainsTable
+  },
+  computed: {
+    labels() {
+      return {
+        domains: this.$gettext("Domains")
+      }
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>