mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Handling pagination and filtering options in audit trail.
This commit is contained in:
@@ -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,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 +0,0 @@
|
||||
VUE_APP_API_BASE_URL=http://localhost:8000/api/v2
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"API_BASE_URL": "http://localhost:8000/api/v2"
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"),
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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")),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user