mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Reintroduced News feed widget in admin dashboard.
This commit is contained in:
@@ -7,4 +7,7 @@ export default {
|
||||
getNotifications() {
|
||||
return repository.get('/admin/notifications/')
|
||||
},
|
||||
getNewsFeed() {
|
||||
return repository.get('/admin/news_feed/')
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<v-card :title="$gettext('Latest news')">
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="news"
|
||||
hide-default-header
|
||||
hide-default-footer
|
||||
>
|
||||
<template #[`item.title`]="{ item }">
|
||||
<a :href="item.link" target="_blank">{{ item.title }}</a>
|
||||
</template>
|
||||
<template #[`item.published`]="{ item }">
|
||||
{{ $date(item.published) }}
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center">
|
||||
<div>
|
||||
<a href="https://modoboa.org/weblog/" target="_blank">
|
||||
{{ $gettext('Visit the official weblog for more information') }}
|
||||
</a>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import adminApi from '@/api/admin'
|
||||
|
||||
const news = ref([])
|
||||
|
||||
const headers = [
|
||||
{ title: '', key: 'title' },
|
||||
{ title: '', key: 'published', align: 'end' },
|
||||
]
|
||||
|
||||
const resp = await adminApi.getNewsFeed()
|
||||
news.value = resp.data
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
|
||||
&:visited {
|
||||
color: rgb(var(--v-theme-primary));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,25 +2,18 @@
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Welcome to Modoboa') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-alert type="info" border="top" colored-border elevation="2" class="mt-10">
|
||||
<p>{{ $gettext('This page is still under construction.') }}</p>
|
||||
<p>
|
||||
{{
|
||||
$gettext(
|
||||
"It will present a summary of your Modoboa server's status and some useful information about the product."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
Your feedback would be really appreciated, you can use
|
||||
<a href="https://github.com/modoboa/modoboa/discussions"
|
||||
>Github discussions</a
|
||||
>
|
||||
to share your ideas and/or comments.
|
||||
</p>
|
||||
</v-alert>
|
||||
|
||||
<v-row>
|
||||
<v-col sm="12" md="6">
|
||||
<NewsFeedWidget />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NewsFeedWidget from '@/components/admin/dashboard/NewsFeedWidget'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
|
||||
@@ -647,3 +647,10 @@ class ModoboaApplicationSerializer(serializers.Serializer):
|
||||
icon = serializers.CharField()
|
||||
url = serializers.CharField()
|
||||
description = serializers.CharField(required=False)
|
||||
|
||||
|
||||
class NewsFeedEntrySerializer(serializers.Serializer):
|
||||
|
||||
title = serializers.CharField()
|
||||
link = serializers.CharField()
|
||||
published = serializers.DateTimeField()
|
||||
|
||||
@@ -543,3 +543,42 @@ class CapabilitiesAPITestCase(ModoAPITestCase):
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("rspamd", response.json()["capabilities"])
|
||||
|
||||
|
||||
class NewsFeedAPIViewTestCase(ModoAPITestCase):
|
||||
|
||||
def test_get(self):
|
||||
url = reverse("v2:news-feed")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.json()), 5)
|
||||
|
||||
@override_settings(DISABLE_DASHBOARD_EXTERNAL_QUERIES=False)
|
||||
def test_if_disabling_from_settings_works(self):
|
||||
url = reverse("v2:news-feed")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.json()), 0)
|
||||
|
||||
def test_custom_rss_feed(self):
|
||||
self.set_global_parameter(
|
||||
"rss_feed_url", "https://www.djangoproject.com/rss/weblog/"
|
||||
)
|
||||
# Try as superadmin
|
||||
url = reverse("v2:news-feed")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("modoboa", response.json()[0]["link"])
|
||||
|
||||
# Try as domainadmin
|
||||
dadmin = models.User.objects.get(username="admin@test.com")
|
||||
self.authenticate_user(dadmin)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("django", response.json()[0]["link"])
|
||||
|
||||
# Try fallback
|
||||
self.set_global_parameter("rss_feed_url", "")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("modoboa", response.json()[0]["link"])
|
||||
|
||||
@@ -37,5 +37,10 @@ urlpatterns += [
|
||||
views.NotificationsAPIView.as_view(),
|
||||
name="notifications",
|
||||
),
|
||||
path(
|
||||
"admin/news_feed/",
|
||||
views.NewsFeedAPIView.as_view(),
|
||||
name="news-feed",
|
||||
),
|
||||
path("capabilities/", views.CapabilitiesAPIView.as_view(), name="capabilities"),
|
||||
]
|
||||
|
||||
@@ -4,6 +4,10 @@ from functools import reduce
|
||||
import logging
|
||||
from smtplib import SMTPException
|
||||
|
||||
from dateutil import parser
|
||||
import feedparser
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.datastructures import MultiValueDictKeyError
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
@@ -12,7 +16,7 @@ from rest_framework.views import APIView
|
||||
|
||||
from modoboa.core import signals
|
||||
from modoboa.core.utils import check_for_updates, get_capabilities
|
||||
from modoboa.lib.permissions import IsSuperUser
|
||||
from modoboa.lib.permissions import IsSuperUser, IsPrivilegedUser
|
||||
from modoboa.lib.throttle import (
|
||||
UserLesserDdosUser,
|
||||
PasswordResetApplyThrottle,
|
||||
@@ -24,6 +28,8 @@ from . import serializers
|
||||
|
||||
logger = logging.getLogger("modoboa.auth")
|
||||
|
||||
MODOBOA_WEBSITE_URL = "https://modoboa.org/"
|
||||
|
||||
|
||||
def delete_cache_key(class_target, throttles: list, request) -> None:
|
||||
"""Attempt to delete cache key from throttling on login/password reset success."""
|
||||
@@ -165,9 +171,34 @@ class NotificationsAPIView(APIView):
|
||||
|
||||
class CapabilitiesAPIView(APIView):
|
||||
"""Return the capability of this Modoboa instance."""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
throttle_classes = [UserLesserDdosUser]
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return response.Response({"capabilities": get_capabilities()})
|
||||
|
||||
|
||||
class NewsFeedAPIView(APIView):
|
||||
"""Return list of latest news from configured RSS feed."""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated, IsPrivilegedUser]
|
||||
throttle_classes = [UserLesserDdosUser]
|
||||
|
||||
@extend_schema(responses=serializers.NewsFeedEntrySerializer(many=True))
|
||||
def get(self, request, *args, **kwargs):
|
||||
lang = "fr" if request.user.language == "fr" else "en"
|
||||
feed_url = f"{MODOBOA_WEBSITE_URL}{lang}/weblog/feeds/"
|
||||
if request.user.role != "SuperAdmins":
|
||||
custom_feed_url = request.localconfig.parameters.get_value("rss_feed_url")
|
||||
if custom_feed_url:
|
||||
feed_url = custom_feed_url
|
||||
entries = []
|
||||
if not settings.DISABLE_DASHBOARD_EXTERNAL_QUERIES:
|
||||
posts = feedparser.parse(feed_url)
|
||||
for entry in posts["entries"][:5]:
|
||||
entry["published"] = parser.parse(entry["published"])
|
||||
entries.append(entry)
|
||||
|
||||
serializer = serializers.NewsFeedEntrySerializer(entries, many=True)
|
||||
return response.Response(serializer.data)
|
||||
|
||||
@@ -154,6 +154,13 @@ class IsSuperUser(permissions.BasePermission):
|
||||
return request.user.is_superuser
|
||||
|
||||
|
||||
class IsPrivilegedUser(permissions.BasePermission):
|
||||
"""Permission class to allow any privileged user."""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.role != core_constants.SIMPLEUSERS_ROLE[0]
|
||||
|
||||
|
||||
class CanCreateDomain(permissions.BasePermission):
|
||||
"""Permissions class to allow users with add_domain right."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user