Handling pagination and filtering options in audit trail.

This commit is contained in:
Antoine Nguyen
2021-06-17 14:57:37 +02:00
parent 5de1308e32
commit f27cfa214f
20 changed files with 159 additions and 108 deletions
+9 -1
View File
@@ -156,7 +156,15 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install dependencies
- name: Build frontend
shell: bash -l {0}
run: |
cd frontend
nvm install 12
yarn install
yarn build
cd ..
- name: Build packages
run: |
sudo apt-get install librrd-dev rrdtool libssl-dev gettext
python -m pip install --upgrade pip setuptools wheel
+1
View File
@@ -1,6 +1,7 @@
include README.rst CONTRIBUTING.rst LICENSE requirements.txt
include modoboa/admin/tests/dovecot
recursive-include modoboa *.html *.js *.css *.png *.po *.mo *.json *.gif *.ttf *.woff *.woff2
recursive-include frontend/dist *.js *.json *.ico *.html *.css *.png *.map
recursive-include modoboa/core/templates *.tpl
recursive-include modoboa/core/commands/templates *.tpl
recursive-include doc *
-1
View File
@@ -1 +0,0 @@
VUE_APP_API_BASE_URL=http://localhost:8000/api/v2
+3
View File
@@ -0,0 +1,3 @@
{
"API_BASE_URL": "http://localhost:8000/api/v2"
}
+2 -2
View File
@@ -3,7 +3,7 @@ import repository from './repository'
const resource = 'logs'
export default {
getAll () {
return repository.get(`/${resource}/`)
getAll (params) {
return repository.get(`/${resource}/`, { params })
}
}
+3 -2
View File
@@ -1,15 +1,16 @@
import Vue from 'vue'
import axios from 'axios'
import Cookies from 'js-cookie'
import router from '../router'
import store from '../store'
const baseURL = process.env.VUE_APP_API_BASE_URL || ''
const _axios = axios.create({ baseURL })
const _axios = axios.create()
_axios.interceptors.request.use(
function (config) {
// Do something before request is sent
config.baseURL = Vue.prototype.$config.API_BASE_URL
return config
},
function (error) {
+34 -10
View File
@@ -17,7 +17,8 @@
:items="logs"
:search="search"
class="elevation-1"
:options.sync="pagination"
:options.sync="options"
:server-items-length="total"
>
<template v-slot:item.level="{ item }">
<v-chip :color="getLevelColor(item.level)" text-color="white" small>
@@ -42,17 +43,11 @@ export default {
{ text: 'Message', value: 'message' }
],
loading: true,
pagination: {
},
search: ''
options: {},
search: '',
total: 0
}
},
created () {
logs.getAll().then(response => {
this.logs = response.data
this.loading = false
})
},
methods: {
getLevelColor (level) {
if (level === 'INFO') {
@@ -65,6 +60,35 @@ export default {
return 'red'
}
return ''
},
fetchLogs () {
let order
if (this.options.sortBy.length) {
order = (this.options.sortDesc[0] ? '-' : '') + this.options.sortBy[0]
}
const params = {
ordering: order,
page: this.options.page,
page_size: this.options.itemsPerPage,
search: this.search
}
this.loading = true
logs.getAll(params).then(response => {
this.logs = response.data.results
this.total = response.data.count
this.loading = false
})
}
},
watch: {
options: {
handler () {
this.fetchLogs()
},
deep: true
},
search (val) {
this.fetchLogs()
}
}
}
+11 -6
View File
@@ -13,9 +13,14 @@ Vue.config.productionTip = false
export const bus = new Vue()
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
fetch(process.env.BASE_URL + 'config.json').then(resp => {
resp.json().then((config) => {
Vue.prototype.$config = config
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
})
})
+3 -3
View File
@@ -2623,9 +2623,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135:
version "1.0.30001144"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz#bca0fffde12f97e1127a351fec3bfc1971aa3b3d"
integrity sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ==
version "1.0.30001236"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz"
integrity sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ==
case-sensitive-paths-webpack-plugin@^2.3.0:
version "2.3.0"
-27
View File
@@ -1,27 +0,0 @@
"""Default admin app settings."""
import os
PLUGIN_BASE_DIR = os.path.dirname(__file__)
ADMIN_STATS_FILES = {
"dev": os.path.join(
PLUGIN_BASE_DIR, "../../frontend/webpack-stats.json"),
"prod": os.path.join(
PLUGIN_BASE_DIR, "static/admin/webpack-stats.json")
}
def apply(settings):
"""Modify settings."""
DEBUG = settings['DEBUG']
if "webpack_loader" not in settings["INSTALLED_APPS"]:
settings["INSTALLED_APPS"] += ("webpack_loader", )
settings["WEBPACK_LOADER"] = {
"ADMIN": {
"CACHE": not DEBUG,
"BUNDLE_DIR_NAME": "admin/",
"STATS_FILE": ADMIN_STATS_FILES.get("dev" if DEBUG else "prod"),
"IGNORE": [".+\.hot-update.js", ".+\.map"]
}
}
-24
View File
@@ -1,24 +0,0 @@
{% load i18n lib_tags static %}
{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modoboa</title>
{% if not debug %}
{% render_bundle 'app' 'css' 'ADMIN' %}
{% endif %}
</head>
<body>
<div id="app"></div>
{% if not debug %}
{% render_bundle 'manifest' 'js' 'ADMIN' %}
{% render_bundle 'vendor' 'js' 'ADMIN' %}
{% render_bundle 'app' 'js' 'ADMIN' %}
{% else %}
{% render_bundle 'app' 'js' 'ADMIN' %}
{% endif %}
</body>
</html>
+2 -4
View File
@@ -5,15 +5,13 @@ from django.urls import path
from . import views
from .views import (
alias as alias_views, domain as domain_views, export as export_views,
identity as identity_views, import_ as import_views,
base as base_views
identity as identity_views, import_ as import_views
)
app_name = "admin"
urlpatterns = [
# path('', domain_views.index, name="index"),
path('', base_views.AdminIndexView.as_view(), name="index"),
path('', domain_views.index, name="index"),
path('domains/', domain_views.domains, name="domain_list"),
path('domains/<int:pk>/', views.DomainDetailView.as_view(),
name="domain_detail"),
-2
View File
@@ -4,7 +4,6 @@ from .alias import AliasDetailView
from .dns import DNSBLDomainDetailView, MXDomainDetailView
from .domain import DomainAlarmsView, DomainDetailView
from .identity import AccountDetailView
from .base import AdminIndexView
__all__ = [
"AccountDetailView",
@@ -13,5 +12,4 @@ __all__ = [
"DomainAlarmsView",
"DomainDetailView",
"MXDomainDetailView",
"AdminIndexView",
]
-11
View File
@@ -1,11 +0,0 @@
"""Admin views."""
from django.views import generic
from django.contrib.auth import mixins as auth_mixins
class AdminIndexView(auth_mixins.LoginRequiredMixin, generic.TemplateView):
"""Admin index view."""
template_name = "admin/index.html"
+13 -1
View File
@@ -1,7 +1,13 @@
"""Core API urls."""
from rest_framework import routers
from django.urls import path
from rest_framework import routers
from rest_framework_simplejwt.views import (
TokenRefreshView,
)
from . import views
from . import viewsets
@@ -10,3 +16,9 @@ router.register(r"account", viewsets.AccountViewSet, basename="account")
router.register(r"logs", viewsets.LogViewSet)
urlpatterns = router.urls
urlpatterns += [
path('token/', views.TokenObtainPairView.as_view(),
name="token_obtain_pair"),
path('token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
]
+68
View File
@@ -0,0 +1,68 @@
"""Core API v2 views."""
import logging
from django.utils.translation import ugettext as _
from rest_framework import response, status
from rest_framework_simplejwt import views as jwt_views
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from modoboa.core.password_hashers import get_password_hasher
from modoboa.parameters import tools as param_tools
logger = logging.getLogger("modoboa.auth")
class TokenObtainPairView(jwt_views.TokenObtainPairView):
"""We overwrite this view to deal with password scheme update."""
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
logger.warning(
_("Failed connection attempt from '%s' as user '%s'"),
request.META["REMOTE_ADDR"],
serializer.data["username"]
)
raise InvalidToken(e.args[0])
user = serializer.user
logger.info(
_("User '%s' successfully logged in"), user.username
)
if user and user.is_active:
condition = (
user.is_local and
param_tools.get_global_parameter(
"update_scheme", raise_exception=False)
)
if condition:
# check if password scheme is correct
scheme = param_tools.get_global_parameter(
"password_scheme", raise_exception=False)
# use SHA512CRYPT as default fallback
if scheme is None:
pwhash = get_password_hasher("sha512crypt")()
else:
pwhash = get_password_hasher(scheme)()
if not user.password.startswith(pwhash.scheme):
logger.info(
_("Password scheme mismatch. Updating %s password"),
user.username
)
user.set_password(serializer.data["password"])
user.save()
if pwhash.needs_rehash(user.password):
logger.info(
_("Password hash parameter missmatch. "
"Updating %s password"),
user.username
)
user.set_password(serializer.data["password"])
user.save()
return response.Response(
serializer.validated_data, status=status.HTTP_200_OK)
+10 -1
View File
@@ -1,7 +1,7 @@
"""Core API v2 viewsets."""
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, response, viewsets
from rest_framework import filters, pagination, permissions, response, viewsets
from rest_framework.decorators import action
from rest_framework_simplejwt.tokens import RefreshToken
@@ -38,12 +38,21 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
})
class CustomPageNumberPagination(pagination.PageNumberPagination):
page_size_query_param = "page_size"
class LogViewSet(viewsets.ReadOnlyModelViewSet):
"""Log viewset."""
filter_backends = [filters.OrderingFilter, filters.SearchFilter]
ordering = ["-date_created"]
ordering_fields = "__all__"
pagination_class = CustomPageNumberPagination
permission_classes = (
permissions.IsAuthenticated,
permissions.DjangoModelPermissions,
)
queryset = models.Log.objects.all()
search_fields = ["logger", "level", "message"]
serializer_class = serializers.LogSerializer
@@ -350,10 +350,6 @@ SILENCED_SYSTEM_CHECKS = [
PHONENUMBER_DB_FORMAT = 'INTERNATIONAL'
# Load admin settings
from modoboa.admin import settings as admin_settings
admin_settings.apply(globals())
# Load settings from extensions
{% for extension in extra_settings %}
try:
-8
View File
@@ -2,19 +2,11 @@
from django.urls import include, path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
from modoboa.core.extensions import exts_pool
app_name = "api"
urlpatterns = [
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
path('', include("modoboa.core.api.v2.urls")),
path('', include("modoboa.admin.api.v2.urls")),
path('', include("modoboa.parameters.api.v2.urls")),
-1
View File
@@ -3,7 +3,6 @@ django-ckeditor==6.1.0
django-phonenumber-field[phonenumbers]==5.1.0
django-reversion==3.0.9
django-xforwardedfor-middleware==2.0
django-webpack-loader
django-otp==1.0.5
django-filter