diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py
index b995afcaa0e28cf32f1746a198a46e05d0e5e466..161c581025da4c68d33de0277d956ed710f1b4ed 100644
--- a/api/funkwhale_api/common/serializers.py
+++ b/api/funkwhale_api/common/serializers.py
@@ -2,10 +2,10 @@ from rest_framework import serializers
 
 
 class Action(object):
-    def __init__(self, name, allow_all=False, filters=None):
+    def __init__(self, name, allow_all=False, qs_filter=None):
         self.name = name
         self.allow_all = allow_all
-        self.filters = filters or {}
+        self.qs_filter = qs_filter
 
     def __repr__(self):
         return "<Action {}>".format(self.name)
@@ -65,7 +65,6 @@ class ActionSerializer(serializers.Serializer):
                 "You cannot apply this action on all objects"
             )
         final_filters = data.get("filters", {}) or {}
-        final_filters.update(data["action"].filters)
         if self.filterset_class and final_filters:
             qs_filterset = self.filterset_class(final_filters, queryset=data["objects"])
             try:
@@ -74,6 +73,9 @@ class ActionSerializer(serializers.Serializer):
                 raise serializers.ValidationError("Invalid filters")
             data["objects"] = qs_filterset.qs
 
+        if data["action"].qs_filter:
+            data["objects"] = data["action"].qs_filter(data["objects"])
+
         data["count"] = data["objects"].count()
         if data["count"] < 1:
             raise serializers.ValidationError("No object matching your request")
diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py
index 5f83ebf1a3143e201891448e2f43206c14015d7c..8098ef1a2f49ee8b6275351a56faae77eb7e8dd6 100644
--- a/api/funkwhale_api/manage/filters.py
+++ b/api/funkwhale_api/manage/filters.py
@@ -2,6 +2,7 @@ from django_filters import rest_framework as filters
 
 from funkwhale_api.common import fields
 from funkwhale_api.music import models as music_models
+from funkwhale_api.requests import models as requests_models
 from funkwhale_api.users import models as users_models
 
 
@@ -50,3 +51,13 @@ class ManageInvitationFilterSet(filters.FilterSet):
         if value is None:
             return queryset
         return queryset.open(value)
+
+
+class ManageImportRequestFilterSet(filters.FilterSet):
+    q = fields.SearchFilter(
+        search_fields=["user__username", "albums", "artist_name", "comment"]
+    )
+
+    class Meta:
+        model = requests_models.ImportRequest
+        fields = ["q", "status"]
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index c639d3a3c2ad26301c203df9c2f393df56f8e926..db5b9272675a6ec8fc3461ebaf69da3ef1cd5088 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -1,8 +1,10 @@
 from django.db import transaction
+from django.utils import timezone
 from rest_framework import serializers
 
 from funkwhale_api.common import serializers as common_serializers
 from funkwhale_api.music import models as music_models
+from funkwhale_api.requests import models as requests_models
 from funkwhale_api.users import models as users_models
 
 from . import filters
@@ -154,9 +156,79 @@ class ManageInvitationSerializer(serializers.ModelSerializer):
 
 
 class ManageInvitationActionSerializer(common_serializers.ActionSerializer):
-    actions = [common_serializers.Action("delete", allow_all=False)]
+    actions = [
+        common_serializers.Action(
+            "delete", allow_all=False, qs_filter=lambda qs: qs.open()
+        )
+    ]
     filterset_class = filters.ManageInvitationFilterSet
 
     @transaction.atomic
     def handle_delete(self, objects):
         return objects.delete()
+
+
+class ManageImportRequestSerializer(serializers.ModelSerializer):
+    user = ManageUserSimpleSerializer(required=False)
+
+    class Meta:
+        model = requests_models.ImportRequest
+        fields = [
+            "id",
+            "status",
+            "creation_date",
+            "imported_date",
+            "user",
+            "albums",
+            "artist_name",
+            "comment",
+        ]
+        read_only_fields = [
+            "id",
+            "status",
+            "creation_date",
+            "imported_date",
+            "user",
+            "albums",
+            "artist_name",
+            "comment",
+        ]
+
+    def validate_code(self, value):
+        if not value:
+            return value
+        if users_models.Invitation.objects.filter(code__iexact=value).exists():
+            raise serializers.ValidationError(
+                "An invitation with this code already exists"
+            )
+        return value
+
+
+class ManageImportRequestActionSerializer(common_serializers.ActionSerializer):
+    actions = [
+        common_serializers.Action(
+            "mark_closed",
+            allow_all=True,
+            qs_filter=lambda qs: qs.filter(status__in=["pending", "accepted"]),
+        ),
+        common_serializers.Action(
+            "mark_imported",
+            allow_all=True,
+            qs_filter=lambda qs: qs.filter(status__in=["pending", "accepted"]),
+        ),
+        common_serializers.Action("delete", allow_all=False),
+    ]
+    filterset_class = filters.ManageImportRequestFilterSet
+
+    @transaction.atomic
+    def handle_delete(self, objects):
+        return objects.delete()
+
+    @transaction.atomic
+    def handle_mark_closed(self, objects):
+        return objects.update(status="closed")
+
+    @transaction.atomic
+    def handle_mark_imported(self, objects):
+        now = timezone.now()
+        return objects.update(status="imported", imported_date=now)
diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py
index 3d4e15db9327855ff4df5f983fe8a70dba26d452..8285ade0699b45e49cc45e654bfa1baa467406ee 100644
--- a/api/funkwhale_api/manage/urls.py
+++ b/api/funkwhale_api/manage/urls.py
@@ -5,6 +5,10 @@ from . import views
 
 library_router = routers.SimpleRouter()
 library_router.register(r"track-files", views.ManageTrackFileViewSet, "track-files")
+requests_router = routers.SimpleRouter()
+requests_router.register(
+    r"import-requests", views.ManageImportRequestViewSet, "import-requests"
+)
 users_router = routers.SimpleRouter()
 users_router.register(r"users", views.ManageUserViewSet, "users")
 users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations")
@@ -12,4 +16,7 @@ users_router.register(r"invitations", views.ManageInvitationViewSet, "invitation
 urlpatterns = [
     url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
     url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
+    url(
+        r"^requests/", include((requests_router.urls, "instance"), namespace="requests")
+    ),
 ]
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index ae3c08a57c829dbc8330c37568a3043fa5f8484e..89d2afe4593f5fe0c34118af9976e43307feaf05 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -3,6 +3,7 @@ from rest_framework.decorators import list_route
 
 from funkwhale_api.common import preferences
 from funkwhale_api.music import models as music_models
+from funkwhale_api.requests import models as requests_models
 from funkwhale_api.users import models as users_models
 from funkwhale_api.users.permissions import HasUserPermission
 
@@ -10,10 +11,7 @@ from . import filters, serializers
 
 
 class ManageTrackFileViewSet(
-    mixins.ListModelMixin,
-    mixins.RetrieveModelMixin,
-    mixins.DestroyModelMixin,
-    viewsets.GenericViewSet,
+    mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
 ):
     queryset = (
         music_models.TrackFile.objects.all()
@@ -69,7 +67,6 @@ class ManageInvitationViewSet(
     mixins.ListModelMixin,
     mixins.RetrieveModelMixin,
     mixins.UpdateModelMixin,
-    mixins.DestroyModelMixin,
     viewsets.GenericViewSet,
 ):
     queryset = (
@@ -96,3 +93,31 @@ class ManageInvitationViewSet(
         serializer.is_valid(raise_exception=True)
         result = serializer.save()
         return response.Response(result, status=200)
+
+
+class ManageImportRequestViewSet(
+    mixins.ListModelMixin,
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    viewsets.GenericViewSet,
+):
+    queryset = (
+        requests_models.ImportRequest.objects.all()
+        .order_by("-id")
+        .select_related("user")
+    )
+    serializer_class = serializers.ManageImportRequestSerializer
+    filter_class = filters.ManageImportRequestFilterSet
+    permission_classes = (HasUserPermission,)
+    required_permissions = ["library"]
+    ordering_fields = ["creation_date", "imported_date"]
+
+    @list_route(methods=["post"])
+    def action(self, request, *args, **kwargs):
+        queryset = self.get_queryset()
+        serializer = serializers.ManageImportRequestActionSerializer(
+            request.data, queryset=queryset
+        )
+        serializer.is_valid(raise_exception=True)
+        result = serializer.save()
+        return response.Response(result, status=200)
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 8b638ce7daff025cd7d68eaba2d93dcb7ec1f562..4f5e3dfc66b7b83247d9dc628176739c29498caf 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -539,7 +539,7 @@ class ImportBatch(models.Model):
         related_name="import_batches",
         null=True,
         blank=True,
-        on_delete=models.CASCADE,
+        on_delete=models.SET_NULL,
     )
 
     class Meta:
diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py
index dbbd38a0dc442bc04358eef8e14ff4e2a89310a9..e07bf8e826bfec8ebe77de5be57b1b7ce9f2d553 100644
--- a/api/tests/common/test_serializers.py
+++ b/api/tests/common/test_serializers.py
@@ -32,7 +32,11 @@ class TestDangerousSerializer(serializers.ActionSerializer):
 
 
 class TestDeleteOnlyInactiveSerializer(serializers.ActionSerializer):
-    actions = [serializers.Action("test", allow_all=True, filters={"is_active": False})]
+    actions = [
+        serializers.Action(
+            "test", allow_all=True, qs_filter=lambda qs: qs.filter(is_active=False)
+        )
+    ]
     filterset_class = TestActionFilterSet
 
     def handle_test(self, objects):
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index 2f0c6bc2568e4a2a7c9e9755a00eda50ecd9dd92..9742b098d2026bd5c0da09b810bc29bea9f91a50 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -31,3 +31,44 @@ def test_user_update_permission(factories):
     assert user.permission_upload is True
     assert user.permission_library is False
     assert user.permission_settings is True
+
+
+def test_manage_import_request_mark_closed(factories):
+    affected = factories["requests.ImportRequest"].create_batch(
+        size=5, status="pending"
+    )
+    # we do not update imported requests
+    factories["requests.ImportRequest"].create_batch(size=5, status="imported")
+    s = serializers.ManageImportRequestActionSerializer(
+        queryset=affected[0].__class__.objects.all(),
+        data={"objects": "all", "action": "mark_closed"},
+    )
+
+    assert s.is_valid(raise_exception=True) is True
+    s.save()
+
+    assert affected[0].__class__.objects.filter(status="imported").count() == 5
+    for ir in affected:
+        ir.refresh_from_db()
+        assert ir.status == "closed"
+
+
+def test_manage_import_request_mark_imported(factories, now):
+    affected = factories["requests.ImportRequest"].create_batch(
+        size=5, status="pending"
+    )
+    # we do not update closed requests
+    factories["requests.ImportRequest"].create_batch(size=5, status="closed")
+    s = serializers.ManageImportRequestActionSerializer(
+        queryset=affected[0].__class__.objects.all(),
+        data={"objects": "all", "action": "mark_imported"},
+    )
+
+    assert s.is_valid(raise_exception=True) is True
+    s.save()
+
+    assert affected[0].__class__.objects.filter(status="closed").count() == 5
+    for ir in affected:
+        ir.refresh_from_db()
+        assert ir.status == "imported"
+        assert ir.imported_date == now
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index d54fca5ddafe3b570bf0d671846587b8132c4f77..baf816fc860ba7d2dbc3b1f6dbdfdc8d2187b541 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.ManageTrackFileViewSet, ["library"], "and"),
         (views.ManageUserViewSet, ["settings"], "and"),
         (views.ManageInvitationViewSet, ["settings"], "and"),
+        (views.ManageImportRequestViewSet, ["library"], "and"),
     ],
 )
 def test_permissions(assert_user_permission, view, permissions, operator):
@@ -63,3 +64,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_music_requests_view(factories, superuser_api_client, mocker):
+    invitations = factories["requests.ImportRequest"].create_batch(size=5)
+    qs = invitations[0].__class__.objects.order_by("-id")
+    url = reverse("api:v1:manage:requests:import-requests-list")
+
+    response = superuser_api_client.get(url, {"sort": "-id"})
+    expected = serializers.ManageImportRequestSerializer(qs, many=True).data
+
+    assert response.data["count"] == len(invitations)
+    assert response.data["results"] == expected
diff --git a/changes/changelog.d/190.feature b/changes/changelog.d/190.feature
new file mode 100644
index 0000000000000000000000000000000000000000..460fb57d14c7fb1c361bd6153839f83997ddb549
--- /dev/null
+++ b/changes/changelog.d/190.feature
@@ -0,0 +1 @@
+Added admin interface to manage import requests (#190)
diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue
index 87c374a336780d1e2623f85685e581abbabab347..065a0a03a76b039100a1f930e1a346e07a5bfc36 100644
--- a/front/src/components/Sidebar.vue
+++ b/front/src/components/Sidebar.vue
@@ -58,21 +58,16 @@
         <div class="item" v-if="showAdmin">
           <div class="header">{{ $t('Administration') }}</div>
           <div class="menu">
-            <router-link
-              class="item"
-              v-if="$store.state.auth.availablePermissions['library']"
-              :to="{name: 'library.requests', query: {status: 'pending' }}">
-              <i class="download icon"></i>{{ $t('Import requests') }}
-              <div
-                :class="['ui', {'teal': notifications.importRequests > 0}, 'label']"
-                :title="$t('Pending import requests')">
-                {{ notifications.importRequests }}</div>
-            </router-link>
             <router-link
               class="item"
               v-if="$store.state.auth.availablePermissions['library']"
               :to="{name: 'manage.library.files'}">
               <i class="book icon"></i>{{ $t('Library') }}
+              <div
+                :class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']"
+                :title="$t('Pending import requests')">
+                {{ $store.state.ui.notifications.importRequests }}</div>
+
             </router-link>
             <router-link
               class="item"
@@ -86,9 +81,9 @@
               :to="{path: '/manage/federation/libraries'}">
               <i class="sitemap icon"></i>{{ $t('Federation') }}
               <div
-                :class="['ui', {'teal': notifications.federation > 0}, 'label']"
+                :class="['ui', {'teal': $store.state.ui.notifications.federation > 0}, 'label']"
                 :title="$t('Pending follow requests')">
-                {{ notifications.federation }}</div>
+                {{ $store.state.ui.notifications.federation }}</div>
             </router-link>
             <router-link
               class="item"
@@ -160,7 +155,6 @@
 
 <script>
 import {mapState, mapActions} from 'vuex'
-import axios from 'axios'
 
 import Player from '@/components/audio/Player'
 import Logo from '@/components/Logo'
@@ -183,11 +177,7 @@ export default {
       selectedTab: 'library',
       backend: backend,
       isCollapsed: true,
-      fetchInterval: null,
-      notifications: {
-        federation: 0,
-        importRequests: 0
-      }
+      fetchInterval: null
     }
   },
   mounted () {
@@ -224,26 +214,8 @@ export default {
       cleanTrack: 'queue/cleanTrack'
     }),
     fetchNotificationsCount () {
-      this.fetchFederationNotificationsCount()
-      this.fetchFederationImportRequestsCount()
-    },
-    fetchFederationNotificationsCount () {
-      if (!this.$store.state.auth.availablePermissions['federation']) {
-        return
-      }
-      let self = this
-      axios.get('federation/libraries/followers/', {params: {pending: true}}).then(response => {
-        self.notifications.federation = response.data.count
-      })
-    },
-    fetchFederationImportRequestsCount () {
-      if (!this.$store.state.auth.availablePermissions['library']) {
-        return
-      }
-      let self = this
-      axios.get('requests/import-requests/', {params: {status: 'pending'}}).then(response => {
-        self.notifications.importRequests = response.data.count
-      })
+      this.$store.dispatch('ui/fetchFederationNotificationsCount')
+      this.$store.dispatch('ui/fetchImportRequestsCount')
     },
     reorder: function (event) {
       this.$store.commit('queue/reorder', {
diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue
index 50337b2291776de826feedad054237957a4855b1..5360de16cd0d959a9dbda011dd5d408ac7f97115 100644
--- a/front/src/components/library/Library.vue
+++ b/front/src/components/library/Library.vue
@@ -6,13 +6,6 @@
       <router-link class="ui item" to="/library/radios" exact><i18next path="Radios"/></router-link>
       <router-link class="ui item" to="/library/playlists" exact><i18next path="Playlists"/></router-link>
       <div class="ui secondary right menu">
-        <router-link
-          v-if="$store.state.auth.authenticated"
-          class="ui item"
-          :to="{name: 'library.requests', query: {status: 'pending' }}"
-          exact>
-          <i18next path="Requests"/>
-        </router-link>
         <router-link v-if="showImports" class="ui item" to="/library/import/launch" exact>
           <i18next path="Import"/>
         </router-link>
diff --git a/front/src/components/manage/library/RequestsTable.vue b/front/src/components/manage/library/RequestsTable.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e51b911a762a46504f4783f376e63ac4180c4ce8
--- /dev/null
+++ b/front/src/components/manage/library/RequestsTable.vue
@@ -0,0 +1,229 @@
+<template>
+  <div>
+    <div class="ui inline form">
+      <div class="fields">
+        <div class="ui field">
+          <label>{{ $t('Search') }}</label>
+          <input type="text" v-model="search" placeholder="Search by artist, username, comment..." />
+        </div>
+        <div class="field">
+          <i18next tag="label" path="Ordering"/>
+          <select class="ui dropdown" v-model="ordering">
+            <option v-for="option in orderingOptions" :value="option[0]">
+              {{ option[1] }}
+            </option>
+          </select>
+        </div>
+        <div class="field">
+          <i18next tag="label" path="Ordering direction"/>
+          <select class="ui dropdown" v-model="orderingDirection">
+            <option value="+">Ascending</option>
+            <option value="-">Descending</option>
+          </select>
+        </div>
+        <div class="field">
+          <label>{{ $t("Status") }}</label>
+          <select class="ui dropdown" v-model="status">
+            <option :value="null">{{ $t('All') }}</option>
+            <option :value="'pending'">{{ $t('Pending') }}</option>
+            <option :value="'accepted'">{{ $t('Accepted') }}</option>
+            <option :value="'imported'">{{ $t('Imported') }}</option>
+            <option :value="'closed'">{{ $t('Closed') }}</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"
+        :action-url="'manage/requests/import-requests/action/'"
+        :filters="actionFilters">
+        <template slot="header-cells">
+          <th>{{ $t('User') }}</th>
+          <th>{{ $t('Status') }}</th>
+          <th>{{ $t('Artist') }}</th>
+          <th>{{ $t('Albums') }}</th>
+          <th>{{ $t('Comment') }}</th>
+          <th>{{ $t('Creation date') }}</th>
+          <th>{{ $t('Import date') }}</th>
+          <th>{{ $t('Actions') }}</th>
+        </template>
+        <template slot="row-cells" slot-scope="scope">
+          <td>
+            {{ scope.obj.user.username }}
+          </td>
+          <td>
+            <span class="ui green basic label" v-if="scope.obj.status === 'imported'">{{ $t('Imported') }}</span>
+            <span class="ui pink basic label" v-else-if="scope.obj.status === 'accepted'">{{ $t('Accepted') }}</span>
+            <span class="ui yellow basic label" v-else-if="scope.obj.status === 'pending'">{{ $t('Pending') }}</span>
+            <span class="ui red basic label" v-else-if="scope.obj.status === 'closed'">{{ $t('Closed') }}</span>
+          </td>
+          <td>
+            <span :title="scope.obj.artist_name">{{ scope.obj.artist_name|truncate(30) }}</span>
+          </td>
+          <td>
+            <span v-if="scope.obj.albums" :title="scope.obj.albums">{{ scope.obj.albums|truncate(30) }}</span>
+            <template v-else>{{ $t('N/A') }}</template>
+          </td>
+          <td>
+            <span v-if="scope.obj.comment" :title="scope.obj.comment">{{ scope.obj.comment|truncate(30) }}</span>
+            <template v-else>{{ $t('N/A') }}</template>
+          </td>
+          <td>
+            <human-date :date="scope.obj.creation_date"></human-date>
+          </td>
+          <td>
+            <human-date v-if="scope.obj.imported_date" :date="scope.obj.creation_date"></human-date>
+            <template v-else>{{ $t('N/A') }}</template>
+          </td>
+          <td>
+            <router-link
+              class="ui tiny basic button"
+              :to="{name: 'library.import.launch', query: {request: scope.obj.id}}"
+              v-if="scope.obj.status === 'pending'">{{ $t('Create import') }}</router-link>
+          </td>
+        </template>
+      </action-table>
+    </div>
+    <div>
+      <pagination
+        v-if="result && result.results.length > 0"
+        @page-changed="selectPage"
+        :compact="true"
+        :current="page"
+        :paginate-by="paginateBy"
+        :total="result.count"
+        ></pagination>
+
+      <span v-if="result && result.results.length > 0">
+        {{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}}
+      </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'
+
+export default {
+  mixins: [OrderingMixin],
+  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: 25,
+      search: '',
+      status: null,
+      orderingDirection: defaultOrdering.direction || '+',
+      ordering: defaultOrdering.field,
+      orderingOptions: [
+        ['creation_date', 'Creation date'],
+        ['imported_date', 'Imported date']
+      ]
+
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      let params = _.merge({
+        'page': this.page,
+        'page_size': this.paginateBy,
+        'q': this.search,
+        'status': this.status,
+        'ordering': this.getOrderingAsString()
+      }, this.filters)
+      let self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/requests/import-requests/', {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: {
+    actionFilters () {
+      var currentFilters = {
+        q: this.search
+      }
+      if (this.filters) {
+        return _.merge(currentFilters, this.filters)
+      } else {
+        return currentFilters
+      }
+    },
+    actions () {
+      return [
+        {
+          name: 'delete',
+          label: this.$t('Delete'),
+          isDangerous: true
+        },
+        {
+          name: 'mark_imported',
+          label: this.$t('Mark as imported'),
+          filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 },
+          isDangerous: true
+        },
+        {
+          name: 'mark_closed',
+          label: this.$t('Mark as closed'),
+          filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 },
+          isDangerous: true
+        }
+      ]
+    }
+  },
+  watch: {
+    search (newValue) {
+      this.page = 1
+      this.fetchData()
+    },
+    page () {
+      this.fetchData()
+    },
+    ordering () {
+      this.page = 1
+      this.fetchData()
+    },
+    status () {
+      this.page = 1
+      this.fetchData()
+    },
+    orderingDirection () {
+      this.page = 1
+      this.fetchData()
+    }
+  }
+}
+</script>
diff --git a/front/src/components/requests/RequestsList.vue b/front/src/components/requests/RequestsList.vue
deleted file mode 100644
index 58b7f5fa9ca65561291588c58f92d7039b357c2a..0000000000000000000000000000000000000000
--- a/front/src/components/requests/RequestsList.vue
+++ /dev/null
@@ -1,198 +0,0 @@
-<template>
-  <div v-title="'Import Requests'">
-    <div class="ui vertical stripe segment">
-      <h2 class="ui header">{{ $t('Music requests') }}</h2>
-      <div :class="['ui', {'loading': isLoading}, 'form']">
-        <div class="fields">
-          <div class="field">
-            <label>{{ $t('Search') }}</label>
-            <input type="text" v-model="query" placeholder="Enter an artist name, a username..."/>
-          </div>
-          <div class="field">
-            <label>{{ $t('Status') }}</label>
-            <select class="ui dropdown" v-model="status">
-              <option :value="'any'">{{ $t('Any') }}</option>
-              <option :value="'pending'">{{ $t('Pending') }}</option>
-              <option :value="'accepted'">{{ $t('Accepted') }}</option>
-              <option :value="'imported'">{{ $t('Imported') }}</option>
-              <option :value="'closed'">{{ $t('Closed') }}</option>
-            </select>
-          </div>
-          <div class="field">
-            <label>{{ $t('Ordering') }}</label>
-            <select class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
-                {{ option[1] }}
-              </option>
-            </select>
-          </div>
-          <div class="field">
-            <label>{{ $t('Ordering direction') }}</label>
-            <select class="ui dropdown" v-model="orderingDirection">
-              <option value="+">Ascending</option>
-              <option value="-">Descending</option>
-            </select>
-          </div>
-          <div class="field">
-            <label>{{ $t('Results per page') }}</label>
-            <select class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(25)">25</option>
-              <option :value="parseInt(50)">50</option>
-            </select>
-          </div>
-        </div>
-      </div>
-      <div class="ui hidden divider"></div>
-      <div
-        v-if="result"
-        v-masonry
-        transition-duration="0"
-        item-selector=".column"
-        percent-position="true"
-        stagger="0"
-        class="ui stackable three column doubling grid">
-        <div
-          v-masonry-tile
-          v-if="result.results.length > 0"
-          v-for="request in result.results"
-          :key="request.id"
-          class="column">
-          <request-card class="fluid" :request="request"></request-card>
-        </div>
-      </div>
-      <div class="ui center aligned basic segment">
-        <pagination
-          v-if="result && result.results.length > 0"
-          @page-changed="selectPage"
-          :current="page"
-          :paginate-by="paginateBy"
-          :total="result.count"
-          ></pagination>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import axios from 'axios'
-import _ from 'lodash'
-import $ from 'jquery'
-
-import logger from '@/logging'
-
-import OrderingMixin from '@/components/mixins/Ordering'
-import PaginationMixin from '@/components/mixins/Pagination'
-import RequestCard from '@/components/requests/Card'
-import Pagination from '@/components/Pagination'
-
-const FETCH_URL = 'requests/import-requests/'
-
-export default {
-  mixins: [OrderingMixin, PaginationMixin],
-  props: {
-    defaultQuery: {type: String, required: false, default: ''},
-    defaultStatus: {required: false, default: 'any'}
-  },
-  components: {
-    RequestCard,
-    Pagination
-  },
-  data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
-    return {
-      isLoading: true,
-      result: null,
-      page: parseInt(this.defaultPage),
-      query: this.defaultQuery,
-      paginateBy: parseInt(this.defaultPaginateBy || 12),
-      orderingDirection: defaultOrdering.direction || '+',
-      ordering: defaultOrdering.field,
-      status: this.defaultStatus || 'any'
-    }
-  },
-  created () {
-    this.fetchData()
-  },
-  mounted () {
-    $('.ui.dropdown').dropdown()
-  },
-  methods: {
-    updateQueryString: _.debounce(function () {
-      let query = {
-        query: {
-          query: this.query,
-          page: this.page,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString()
-        }
-      }
-      if (this.status !== 'any') {
-        query.query.status = this.status
-      }
-      this.$router.replace(query)
-    }, 500),
-    fetchData: _.debounce(function () {
-      var self = this
-      this.isLoading = true
-      let url = FETCH_URL
-      let params = {
-        page: this.page,
-        page_size: this.paginateBy,
-        q: this.query,
-        ordering: this.getOrderingAsString()
-      }
-      if (this.status !== 'any') {
-        params.status = this.status
-      }
-      logger.default.debug('Fetching request...')
-      axios.get(url, {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      })
-    }, 500),
-    selectPage: function (page) {
-      this.page = page
-    }
-  },
-  computed: {
-    orderingOptions: function () {
-      return [
-        ['creation_date', this.$t('Creation date')],
-        ['artist_name', this.$t('Artist name')],
-        ['user__username', this.$t('User')]
-      ]
-    }
-  },
-  watch: {
-    page () {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    paginateBy () {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    ordering () {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    orderingDirection () {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    query () {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    status () {
-      this.updateQueryString()
-      this.fetchData()
-    }
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 19474376874435c36e735920290061b7111b3fc4..bb59b5348b5cdbc36178c91b81249d5f7f7e19ec 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -24,13 +24,13 @@ import RadioBuilder from '@/components/library/radios/Builder'
 import RadioDetail from '@/views/radios/Detail'
 import BatchList from '@/components/library/import/BatchList'
 import BatchDetail from '@/components/library/import/BatchDetail'
-import RequestsList from '@/components/requests/RequestsList'
 import PlaylistDetail from '@/views/playlists/Detail'
 import PlaylistList from '@/views/playlists/List'
 import Favorites from '@/components/favorites/List'
 import AdminSettings from '@/views/admin/Settings'
 import AdminLibraryBase from '@/views/admin/library/Base'
 import AdminLibraryFilesList from '@/views/admin/library/FilesList'
+import AdminLibraryRequestsList from '@/views/admin/library/RequestsList'
 import AdminUsersBase from '@/views/admin/users/Base'
 import AdminUsersDetail from '@/views/admin/users/UsersDetail'
 import AdminUsersList from '@/views/admin/users/UsersList'
@@ -184,6 +184,11 @@ export default new Router({
           path: 'files',
           name: 'manage.library.files',
           component: AdminLibraryFilesList
+        },
+        {
+          path: 'requests',
+          name: 'manage.library.requests',
+          component: AdminLibraryRequestsList
         }
       ]
     },
@@ -278,21 +283,7 @@ export default new Router({
           children: [
           ]
         },
-        { path: 'import/batches/:id', name: 'library.import.batches.detail', component: BatchDetail, props: true },
-        {
-          path: 'requests/',
-          name: 'library.requests',
-          component: RequestsList,
-          props: (route) => ({
-            defaultOrdering: route.query.ordering,
-            defaultQuery: route.query.query,
-            defaultPaginateBy: route.query.paginateBy,
-            defaultPage: route.query.page,
-            defaultStatus: route.query.status || 'any'
-          }),
-          children: [
-          ]
-        }
+        { path: 'import/batches/:id', name: 'library.import.batches.detail', component: BatchDetail, props: true }
       ]
     },
     { path: '*', component: PageNotFound }
diff --git a/front/src/store/ui.js b/front/src/store/ui.js
index be744afe51ad954a4bae722f9442a9d71ad85730..c336803475c5c6c79776e501dd94a8884a9198c6 100644
--- a/front/src/store/ui.js
+++ b/front/src/store/ui.js
@@ -1,3 +1,4 @@
+import axios from 'axios'
 
 export default {
   namespaced: true,
@@ -5,7 +6,11 @@ export default {
     lastDate: new Date(),
     maxMessages: 100,
     messageDisplayDuration: 10000,
-    messages: []
+    messages: [],
+    notifications: {
+      federation: 0,
+      importRequests: 0
+    }
   },
   mutations: {
     computeLastDate: (state) => {
@@ -16,6 +21,27 @@ export default {
       if (state.messages.length > state.maxMessages) {
         state.messages.shift()
       }
+    },
+    notifications (state, {type, count}) {
+      state.notifications[type] = count
+    }
+  },
+  actions: {
+    fetchFederationNotificationsCount ({rootState, commit}) {
+      if (!rootState.auth.availablePermissions['federation']) {
+        return
+      }
+      axios.get('federation/libraries/followers/', {params: {pending: true}}).then(response => {
+        commit('notifications', {type: 'federation', count: response.data.count})
+      })
+    },
+    fetchImportRequestsCount ({rootState, commit}) {
+      if (!rootState.auth.availablePermissions['library']) {
+        return
+      }
+      axios.get('requests/import-requests/', {params: {status: 'pending'}}).then(response => {
+        commit('notifications', {type: 'importRequests', count: response.data.count})
+      })
     }
   }
 }
diff --git a/front/src/views/admin/library/Base.vue b/front/src/views/admin/library/Base.vue
index 834fca920f62f195cb83bbd37c4d65dd47ce946a..cc26c8d6be42f0fe30643ea13bb78bccc1e13838 100644
--- a/front/src/views/admin/library/Base.vue
+++ b/front/src/views/admin/library/Base.vue
@@ -4,6 +4,15 @@
       <router-link
         class="ui item"
         :to="{name: 'manage.library.files'}">{{ $t('Files') }}</router-link>
+      <router-link
+        class="ui item"
+        :to="{name: 'manage.library.requests'}">
+          {{ $t('Import requests') }}
+          <div
+            :class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']"
+            :title="$t('Pending import requests')">
+            {{ $store.state.ui.notifications.importRequests }}</div>
+          </router-link>
     </div>
     <router-view :key="$route.fullPath"></router-view>
   </div>
diff --git a/front/src/views/admin/library/RequestsList.vue b/front/src/views/admin/library/RequestsList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..160bf890b99dc42f0cdd07493e06e70f0e3fe93d
--- /dev/null
+++ b/front/src/views/admin/library/RequestsList.vue
@@ -0,0 +1,23 @@
+<template>
+  <div v-title="$t('Import requests')">
+    <div class="ui vertical stripe segment">
+      <h2 class="ui header">{{ $t('Import requests') }}</h2>
+      <div class="ui hidden divider"></div>
+      <library-requests-table></library-requests-table>
+    </div>
+  </div>
+</template>
+
+<script>
+import LibraryRequestsTable from '@/components/manage/library/RequestsTable'
+
+export default {
+  components: {
+    LibraryRequestsTable
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>