mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Improved UI
This commit is contained in:
@@ -31,55 +31,57 @@
|
||||
value="all"
|
||||
/>
|
||||
</v-radio-group>
|
||||
<div v-for="(condition, index) in form.conditions" class="d-flex align-center">
|
||||
<v-select
|
||||
v-model="condition.name"
|
||||
:label="$gettext('Choose a header')"
|
||||
:items="conditionTemplates"
|
||||
item-title="label"
|
||||
item-value="name"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
@update:model-value=""
|
||||
/>
|
||||
<v-select
|
||||
v-model="condition.operator"
|
||||
:label="$gettext('Choose an operator')"
|
||||
:items="getConditionOperators(condition.name)"
|
||||
item-title="label"
|
||||
item-value="name"
|
||||
variant="outlined"
|
||||
class="ml-2"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="condition.value"
|
||||
:label="$gettext('Value')"
|
||||
variant="outlined"
|
||||
class="ml-2"
|
||||
density="compact"
|
||||
:type="getConditionType(index)"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-btn
|
||||
v-if="index === 0"
|
||||
icon="mdi-plus"
|
||||
variant="text"
|
||||
:title="$gettext('Add condition')"
|
||||
color="primary"
|
||||
@click="addCondition"
|
||||
/>
|
||||
<v-btn
|
||||
v-else
|
||||
icon="mdi-trash-can"
|
||||
variant="text"
|
||||
:title="$gettext('Remove condition')"
|
||||
color="error"
|
||||
@click="removeCondition(index)"
|
||||
<template v-if="form.match_type !== 'all'">
|
||||
<div v-for="(condition, index) in form.conditions" class="d-flex align-center">
|
||||
<v-select
|
||||
v-model="condition.name"
|
||||
:label="$gettext('Choose a header')"
|
||||
:items="conditionTemplates"
|
||||
item-title="label"
|
||||
item-value="name"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
@update:model-value=""
|
||||
/>
|
||||
</div>
|
||||
<v-select
|
||||
v-model="condition.operator"
|
||||
:label="$gettext('Choose an operator')"
|
||||
:items="getConditionOperators(condition.name)"
|
||||
item-title="label"
|
||||
item-value="name"
|
||||
variant="outlined"
|
||||
class="ml-2"
|
||||
density="compact"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="condition.value"
|
||||
:label="$gettext('Value')"
|
||||
variant="outlined"
|
||||
class="ml-2"
|
||||
density="compact"
|
||||
:type="getConditionType(index)"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-btn
|
||||
v-if="index === 0"
|
||||
icon="mdi-plus"
|
||||
variant="text"
|
||||
:title="$gettext('Add condition')"
|
||||
color="primary"
|
||||
@click="addCondition"
|
||||
/>
|
||||
<v-btn
|
||||
v-else
|
||||
icon="mdi-trash-can"
|
||||
variant="text"
|
||||
:title="$gettext('Remove condition')"
|
||||
color="error"
|
||||
@click="removeCondition(index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ $gettext('Actions') }}</h4>
|
||||
@@ -143,6 +145,7 @@
|
||||
<v-btn
|
||||
color="primary"
|
||||
:text="submitLabel"
|
||||
:loading="working"
|
||||
@click="submit"
|
||||
></v-btn>
|
||||
</v-card-actions>
|
||||
@@ -175,6 +178,7 @@ const conditionTemplates = ref([])
|
||||
const actionTemplates = ref([])
|
||||
const formRef = ref()
|
||||
const form = ref(getInitialFormContent())
|
||||
const working = ref(false)
|
||||
|
||||
const title = computed(() => {
|
||||
return (props.filter) ? $gettext('Edit filter') : $gettext('New filter')
|
||||
@@ -266,6 +270,7 @@ async function submit() {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
let msg
|
||||
const data = JSON.parse(JSON.stringify(form.value))
|
||||
for (const action of data.actions) {
|
||||
@@ -288,6 +293,8 @@ async function submit() {
|
||||
busStore.displayNotification({ msg: err.response.data.error, type: 'error' })
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
working.value = false
|
||||
}
|
||||
busStore.displayNotification({ msg })
|
||||
close()
|
||||
|
||||
@@ -36,13 +36,18 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useBusStore } from '@/stores'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import rules from '@/plugins/rules'
|
||||
import accountApi from '@/api/account'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const emit = defineEmits(['close'])
|
||||
const busStore = useBusStore()
|
||||
|
||||
const form = ref({})
|
||||
const formRef = ref()
|
||||
const working = ref(false)
|
||||
|
||||
function close() {
|
||||
formRef.value.reset()
|
||||
@@ -55,8 +60,13 @@ async function submit() {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
accountApi.createFilterSet(form.value).then(() => {
|
||||
working.value = true
|
||||
try {
|
||||
await accountApi.createFilterSet(form.value)
|
||||
busStore.displayNotification({ msg: $gettext('Filter set created') })
|
||||
close()
|
||||
})
|
||||
} finally {
|
||||
working.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -257,7 +257,7 @@ const routes = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'filters',
|
||||
path: 'filters/:filterset?',
|
||||
name: 'AccountFilters',
|
||||
component: () => import('@/views/account/FiltersView.vue'),
|
||||
meta: {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
v-model="currentFilterSet"
|
||||
:label="$gettext('Select a filter set')"
|
||||
:items="filterSets"
|
||||
item-title="name"
|
||||
:item-title="getFilterSetName"
|
||||
return-object
|
||||
variant="outlined"
|
||||
hide-details
|
||||
@@ -35,7 +35,7 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
<MenuItems
|
||||
:items="filterSetActions"
|
||||
:items="getFilterSetActions()"
|
||||
:obj="currentFilterSet"
|
||||
/>
|
||||
</v-menu>
|
||||
@@ -103,6 +103,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore } from '@/stores'
|
||||
import accountApi from '@/api/account'
|
||||
@@ -110,6 +111,8 @@ import FilterSetForm from '@/components/account/FilterSetForm.vue'
|
||||
import FilterForm from '@/components/account/FilterForm.vue'
|
||||
import MenuItems from '@/components/tools/MenuItems.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const busStore = useBusStore()
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
@@ -129,9 +132,24 @@ const headers = [
|
||||
watch(currentFilterSet, (value) => {
|
||||
if (value) {
|
||||
fetchFilters(value.name)
|
||||
if (route.params.filterset !== value.name) {
|
||||
router.push({
|
||||
name: 'AccountFilters', params: { filterset: value.name }
|
||||
})
|
||||
}
|
||||
} else {
|
||||
router.push({ name: 'AccountFilters' })
|
||||
}
|
||||
})
|
||||
|
||||
function getFilterSetName(filterSet) {
|
||||
let result = filterSet.name
|
||||
if (filterSet.active) {
|
||||
result += ' (' + $gettext('active') + ')'
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function closeFilterSetForm() {
|
||||
showFilterSetForm.value = false
|
||||
fetchFilterSets()
|
||||
@@ -146,6 +164,14 @@ function closeFilterForm() {
|
||||
function fetchFilterSets() {
|
||||
accountApi.getFilterSets().then(resp => {
|
||||
filterSets.value = resp.data
|
||||
if (!currentFilterSet.value && route.params.filterset) {
|
||||
for (const filterSet of filterSets.value) {
|
||||
if (filterSet.name === route.params.filterset) {
|
||||
currentFilterSet.value = filterSet
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -237,13 +263,7 @@ function getFilterActions(filter) {
|
||||
return result
|
||||
}
|
||||
|
||||
const filterSetActions = ref([
|
||||
{
|
||||
label: $gettext('Activate filter set'),
|
||||
color: 'success',
|
||||
icon: 'mdi-check',
|
||||
onClick: activateFilterSet
|
||||
},
|
||||
const filterSetActions = [
|
||||
{
|
||||
label: $gettext('Download filter set'),
|
||||
icon: 'mdi-download',
|
||||
@@ -255,7 +275,20 @@ const filterSetActions = ref([
|
||||
icon: 'mdi-trash-can',
|
||||
onClick: deleteFilterSet
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
function getFilterSetActions() {
|
||||
const result = []
|
||||
if (!currentFilterSet.value.active) {
|
||||
result.push({
|
||||
label: $gettext('Activate filter set'),
|
||||
color: 'success',
|
||||
icon: 'mdi-check',
|
||||
onClick: activateFilterSet
|
||||
})
|
||||
}
|
||||
return result.concat(filterSetActions)
|
||||
}
|
||||
|
||||
fetchFilterSets()
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from sievelib import commands
|
||||
from sievelib.factory import Filter
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -37,7 +38,7 @@ class FilterSerializer(serializers.Serializer):
|
||||
actions = ActionSerializer(many=True)
|
||||
|
||||
@staticmethod
|
||||
def from_filters(filters) -> "FilterSerializer":
|
||||
def from_filters(filters: List[Filter]) -> "FilterSerializer":
|
||||
result = []
|
||||
for fobj in filters:
|
||||
test = (
|
||||
|
||||
Reference in New Issue
Block a user