mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Added features
This commit is contained in:
@@ -53,9 +53,18 @@ export default {
|
||||
getFilterSets() {
|
||||
return repository.get(`${resource}/filtersets/`)
|
||||
},
|
||||
downloadFilterSet(filterSetName) {
|
||||
return repository.get(`${resource}/filtersets/${filterSetName}/download/`)
|
||||
},
|
||||
createFilterSet(data) {
|
||||
return repository.post(`${resource}/filtersets/`, data)
|
||||
},
|
||||
activateFilterSet(filterSetName) {
|
||||
return repository.post(`${resource}/filtersets/${filterSetName}/activate/`)
|
||||
},
|
||||
deleteFilterSet(filterSetName) {
|
||||
return repository.delete(`${resource}/filtersets/${filterSetName}/`)
|
||||
},
|
||||
getFilterConditionTemplates() {
|
||||
return repository.get(`${resource}/filtersets/condition_templates/`)
|
||||
},
|
||||
@@ -77,6 +86,16 @@ export default {
|
||||
data
|
||||
)
|
||||
},
|
||||
disableFilter(filterSetName, filter) {
|
||||
return repository.post(
|
||||
`${resource}/filtersets/${filterSetName}/filters/${filter}/disable/`
|
||||
)
|
||||
},
|
||||
enableFilter(filterSetName, filter) {
|
||||
return repository.post(
|
||||
`${resource}/filtersets/${filterSetName}/filters/${filter}/enable/`
|
||||
)
|
||||
},
|
||||
deleteFilter(filterSetName, filter) {
|
||||
return repository.delete(
|
||||
`${resource}/filtersets/${filterSetName}/filters/${filter}/`
|
||||
|
||||
@@ -264,17 +264,23 @@ async function submit() {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
if (!props.filter) {
|
||||
accountApi.createFilter(props.filterSet, form.value).then(() => {
|
||||
busStore.displayNotification({ msg: $gettext('Filter added') })
|
||||
close()
|
||||
})
|
||||
} else {
|
||||
accountApi.updateFilter(props.filterSet, originalFilterName, form.value).then(() => {
|
||||
busStore.displayNotification({ msg: $gettext('Filter updated') })
|
||||
close()
|
||||
})
|
||||
let msg
|
||||
try {
|
||||
if (!props.filter) {
|
||||
await accountApi.createFilter(props.filterSet, form.value)
|
||||
msg = $gettext('Filter added')
|
||||
} else {
|
||||
await accountApi.updateFilter(props.filterSet, originalFilterName, form.value)
|
||||
msg = $gettext('Filter updated')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.response.data && err.response.data.error) {
|
||||
busStore.displayNotification({ msg: err.response.data.error, type: 'error' })
|
||||
}
|
||||
return
|
||||
}
|
||||
busStore.displayNotification({ msg })
|
||||
close()
|
||||
}
|
||||
|
||||
accountApi.getFilterConditionTemplates().then(resp => {
|
||||
|
||||
@@ -19,10 +19,26 @@
|
||||
color="primary"
|
||||
icon="mdi-plus"
|
||||
size="small"
|
||||
class="ml-4"
|
||||
@click="showCreationForm = true"
|
||||
class="mx-4"
|
||||
@click="showFilterSetForm = true"
|
||||
:title="$gettext('Add filter set')"
|
||||
/>
|
||||
<v-menu v-if="currentFilterSet">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
append-icon="mdi-chevron-down"
|
||||
v-bind="props"
|
||||
size="small"
|
||||
color="default"
|
||||
>
|
||||
{{ $gettext('Actions') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<MenuItems
|
||||
:items="filterSetActions"
|
||||
:obj="currentFilterSet"
|
||||
/>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -57,7 +73,7 @@
|
||||
>
|
||||
</v-btn>
|
||||
</template>
|
||||
<MenuItems :items="filterActions" :obj="item" />
|
||||
<MenuItems :items="getFilterActions(item)" :obj="item" />
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table>
|
||||
@@ -111,7 +127,9 @@ const headers = [
|
||||
]
|
||||
|
||||
watch(currentFilterSet, (value) => {
|
||||
fetchFilters(value.name)
|
||||
if (value) {
|
||||
fetchFilters(value.name)
|
||||
}
|
||||
})
|
||||
|
||||
function closeFilterSetForm() {
|
||||
@@ -142,6 +160,20 @@ function editFilter(filter) {
|
||||
showFilterForm.value = true
|
||||
}
|
||||
|
||||
function enableFilter(filter) {
|
||||
accountApi.enableFilter(currentFilterSet.value.name, filter.name).then(() => {
|
||||
filter.enabled = true
|
||||
busStore.displayNotification({ msg: $gettext('Filter enabled') })
|
||||
})
|
||||
}
|
||||
|
||||
function disableFilter(filter) {
|
||||
accountApi.disableFilter(currentFilterSet.value.name, filter.name).then(() => {
|
||||
filter.enabled = false
|
||||
busStore.displayNotification({ msg: $gettext('Filter disabled') })
|
||||
})
|
||||
}
|
||||
|
||||
function deleteFilter(filter) {
|
||||
accountApi.deleteFilter(currentFilterSet.value.name, filter.name).then(() => {
|
||||
busStore.displayNotification({ msg: $gettext('Filter removed') })
|
||||
@@ -149,6 +181,28 @@ function deleteFilter(filter) {
|
||||
})
|
||||
}
|
||||
|
||||
async function activateFilterSet(filterSet) {
|
||||
await accountApi.activateFilterSet(filterSet.name)
|
||||
busStore.displayNotification({ msg: $gettext('Filter set activated') })
|
||||
}
|
||||
|
||||
async function deleteFilterSet(filterSet) {
|
||||
await accountApi.deleteFilterSet(filterSet.name)
|
||||
busStore.displayNotification({ msg: $gettext('Filter set removed') })
|
||||
currentFilterSet.value = null
|
||||
fetchFilterSets()
|
||||
}
|
||||
|
||||
async function downloadFilterSet(filterSet) {
|
||||
const resp = await accountApi.downloadFilterSet(filterSet.name)
|
||||
const blob = new Blob([resp.data], { type: 'text/plain' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `${filterSet.name}.txt`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
const filterActions = [
|
||||
{
|
||||
label: $gettext('Edit'),
|
||||
@@ -163,6 +217,46 @@ const filterActions = [
|
||||
}
|
||||
]
|
||||
|
||||
function getFilterActions(filter) {
|
||||
const result = [...filterActions]
|
||||
if (filter.enabled) {
|
||||
result.push({
|
||||
label: $gettext('Disable'),
|
||||
icon: 'mdi-stop',
|
||||
color: 'red',
|
||||
onClick: disableFilter
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
label: $gettext('Enable'),
|
||||
icon: 'mdi-check',
|
||||
color: 'success',
|
||||
onClick: enableFilter
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const filterSetActions = ref([
|
||||
{
|
||||
label: $gettext('Activate filter set'),
|
||||
color: 'success',
|
||||
icon: 'mdi-check',
|
||||
onClick: activateFilterSet
|
||||
},
|
||||
{
|
||||
label: $gettext('Download filter set'),
|
||||
icon: 'mdi-download',
|
||||
onClick: downloadFilterSet
|
||||
},
|
||||
{
|
||||
label: $gettext('Delete filter set'),
|
||||
color: 'error',
|
||||
icon: 'mdi-trash-can',
|
||||
onClick: deleteFilterSet
|
||||
}
|
||||
])
|
||||
|
||||
fetchFilterSets()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ModoboaException(Exception):
|
||||
if "http_code" in kwargs:
|
||||
self.http_code = kwargs["http_code"]
|
||||
del kwargs["http_code"]
|
||||
super(ModoboaException, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class InternalError(ModoboaException):
|
||||
|
||||
@@ -4,6 +4,9 @@ import copy
|
||||
from typing import Optional
|
||||
|
||||
from sievelib.commands import BadArgument, BadValue
|
||||
from sievelib.factory import FilterAlreadyExists
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
@@ -13,6 +16,7 @@ from rest_framework.decorators import action
|
||||
from modoboa.lib.connections import ConnectionError
|
||||
from modoboa.sievefilters import constants
|
||||
from modoboa.sievefilters.lib import SieveClient, SieveClientError
|
||||
from modoboa.sievefilters.rfc6266 import build_header
|
||||
from modoboa.sievefilters.api.v2 import serializers
|
||||
|
||||
|
||||
@@ -31,8 +35,8 @@ class FilterSetViewSet(viewsets.ViewSet):
|
||||
sclient = self.get_sieve_client(request)
|
||||
active_script, scripts = sclient.listscripts()
|
||||
scripts = [
|
||||
{"name": script, "active": active_script == script} for script in scripts
|
||||
]
|
||||
{"name": active_script, "active": True},
|
||||
] + [{"name": script, "active": False} for script in scripts]
|
||||
return response.Response(scripts)
|
||||
|
||||
def create(self, request):
|
||||
@@ -47,6 +51,44 @@ class FilterSetViewSet(viewsets.ViewSet):
|
||||
)
|
||||
return response.Response(serializer.validated_data, 201)
|
||||
|
||||
@action(methods=["post"], detail=True)
|
||||
def activate(self, request, pk):
|
||||
"""Activate an existing filter set."""
|
||||
sclient = self.get_sieve_client(request)
|
||||
try:
|
||||
fset = sclient.getscript(pk, format="fset")
|
||||
except SieveClientError:
|
||||
return response.Response(status=404)
|
||||
try:
|
||||
sclient.activatescript(pk)
|
||||
except SieveClientError as err:
|
||||
return response.Response({"error": str(err)}, status=400)
|
||||
return response.Response()
|
||||
|
||||
@action(methods=["get"], detail=True)
|
||||
def download(self, request, pk):
|
||||
"""Download the content of an existing filter set."""
|
||||
sclient = self.get_sieve_client(request)
|
||||
try:
|
||||
script = sclient.getscript(pk)
|
||||
except SieveClientError:
|
||||
return response.Response(status=404)
|
||||
resp = HttpResponse(script)
|
||||
resp["Content-Type"] = "text/plain; charset=utf-8"
|
||||
resp["Content-Length"] = len(script)
|
||||
resp["Content-Disposition"] = build_header(f"{pk}.txt")
|
||||
return resp
|
||||
|
||||
def destroy(self, request, pk):
|
||||
"""Delete an existing filter set."""
|
||||
sclient = self.get_sieve_client(request)
|
||||
try:
|
||||
fset = sclient.getscript(pk, format="fset")
|
||||
except SieveClientError:
|
||||
return response.Response(status=404)
|
||||
sclient.deletescript(pk)
|
||||
return response.Response(status=204)
|
||||
|
||||
@extend_schema(
|
||||
description="Retrieve the list of available filter condition templates",
|
||||
responses=serializers.ConditionTemplateSerializer,
|
||||
@@ -98,6 +140,10 @@ class FilterSetViewSet(viewsets.ViewSet):
|
||||
fset.addfilter(fltname, conditions, actions, match_type)
|
||||
except (BadArgument, BadValue) as inst:
|
||||
return response.Response(str(inst), status=400)
|
||||
except FilterAlreadyExists:
|
||||
return response.Response(
|
||||
{"error": f"Filter {fset.name} already exists"}, status=409
|
||||
)
|
||||
sclient.pushscript(fset.name, str(fset))
|
||||
return response.Response(serializer.validated_data, status=201)
|
||||
|
||||
@@ -130,6 +176,34 @@ class FilterSetViewSet(viewsets.ViewSet):
|
||||
sclient.pushscript(fset.name, str(fset))
|
||||
return response.Response(serializer.validated_data, status=200)
|
||||
|
||||
@action(
|
||||
methods=["post"], detail=True, url_path="filters/(?P<filter>[^/.]+)/disable"
|
||||
)
|
||||
def disable_filter(self, request, pk, filter: str):
|
||||
"""Disable an existing filter."""
|
||||
sclient = self.get_sieve_client(request)
|
||||
try:
|
||||
fset = sclient.getscript(pk, format="fset")
|
||||
except SieveClientError:
|
||||
return response.Response(status=404)
|
||||
if not fset.disablefilter(filter):
|
||||
return response.Response(status=404)
|
||||
sclient.pushscript(pk, str(fset))
|
||||
return response.Response(status=204)
|
||||
|
||||
@action(methods=["post"], detail=True, url_path="filters/(?P<filter>[^/.]+)/enable")
|
||||
def enable_filter(self, request, pk, filter: str):
|
||||
"""Enable an existing filter."""
|
||||
sclient = self.get_sieve_client(request)
|
||||
try:
|
||||
fset = sclient.getscript(pk, format="fset")
|
||||
except SieveClientError:
|
||||
return response.Response(status=404)
|
||||
if not fset.enablefilter(filter):
|
||||
return response.Response(status=404)
|
||||
sclient.pushscript(pk, str(fset))
|
||||
return response.Response(status=204)
|
||||
|
||||
@extend_schema(
|
||||
description="Delete an existing filter",
|
||||
parameters=[
|
||||
|
||||
@@ -25,6 +25,8 @@ class SieveClientError(ModoboaException):
|
||||
class SieveClient:
|
||||
"""Sieve client."""
|
||||
|
||||
msc: managesieve.Client
|
||||
|
||||
def __init__(self, user: Optional[str] = None, password: Optional[str] = None):
|
||||
if user and password:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user