284 lines
12 KiB
HTML
284 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Filterregeln — IMAP Mail Filter{% endblock %}
|
|
{% block content %}
|
|
<h1>Filterregeln</h1>
|
|
|
|
<label for="account-select">Konto auswählen:</label>
|
|
<select id="account-select" onchange="loadFilters()">
|
|
<option value="">— Konto wählen —</option>
|
|
{% for acc in accounts %}
|
|
<option value="{{ acc.id }}" {{ 'selected' if selected_account_id == acc.id else '' }}>{{ acc.name }} ({{ acc.username }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<div id="filters-container">
|
|
<p>Bitte ein Konto auswählen.</p>
|
|
</div>
|
|
|
|
<dialog id="filter-dialog">
|
|
<article style="min-width: 60vw;">
|
|
<header>
|
|
<button aria-label="Close" rel="prev" onclick="document.getElementById('filter-dialog').close()"></button>
|
|
<h3 id="dialog-title">Neue Filterregel</h3>
|
|
</header>
|
|
<form id="filter-form">
|
|
<input type="hidden" id="filter-id" value="">
|
|
<label>
|
|
Name
|
|
<input type="text" id="filter-name" required placeholder="z.B. Newsletter sortieren">
|
|
</label>
|
|
<div class="grid">
|
|
<label>
|
|
Priorität
|
|
<input type="number" id="filter-priority" value="100">
|
|
</label>
|
|
<label>
|
|
Quellordner
|
|
<input type="text" id="filter-source-folder" value="INBOX">
|
|
</label>
|
|
</div>
|
|
<label>
|
|
<input type="checkbox" id="filter-stop-processing">
|
|
Nach Treffer keine weiteren Regeln anwenden
|
|
</label>
|
|
|
|
<h4>Bedingungen <small>(alle müssen zutreffen)</small></h4>
|
|
<div id="conditions-list"></div>
|
|
<button type="button" class="outline small" onclick="addCondition()">+ Bedingung</button>
|
|
|
|
<h4>Aktionen</h4>
|
|
<div id="actions-list"></div>
|
|
<button type="button" class="outline small" onclick="addAction()">+ Aktion</button>
|
|
|
|
<footer>
|
|
<div role="group">
|
|
<button type="submit">Speichern</button>
|
|
<button type="button" class="outline" onclick="document.getElementById('filter-dialog').close()">Abbrechen</button>
|
|
</div>
|
|
</footer>
|
|
</form>
|
|
</article>
|
|
</dialog>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
const FIELDS = [
|
|
{value: 'from', label: 'Von'},
|
|
{value: 'to', label: 'An'},
|
|
{value: 'subject', label: 'Betreff'},
|
|
{value: 'body', label: 'Text'},
|
|
{value: 'has_attachment', label: 'Hat Anhang'},
|
|
];
|
|
const MATCH_TYPES = [
|
|
{value: 'contains', label: 'enthält'},
|
|
{value: 'regex', label: 'Regex'},
|
|
{value: 'exact', label: 'exakt'},
|
|
];
|
|
const ACTION_TYPES = [
|
|
{value: 'move', label: 'Verschieben in Ordner', needsParam: true, paramLabel: 'Zielordner'},
|
|
{value: 'forward', label: 'Weiterleiten an', needsParam: true, paramLabel: 'E-Mail-Adresse'},
|
|
{value: 'delete', label: 'Löschen', needsParam: false},
|
|
{value: 'mark_read', label: 'Als gelesen markieren', needsParam: false},
|
|
];
|
|
|
|
async function loadFilters() {
|
|
const accountId = document.getElementById('account-select').value;
|
|
const container = document.getElementById('filters-container');
|
|
if (!accountId) {
|
|
container.innerHTML = '<p>Bitte ein Konto auswählen.</p>';
|
|
return;
|
|
}
|
|
const resp = await fetch(`/api/filters/account/${accountId}`);
|
|
const filters = await resp.json();
|
|
if (filters.length === 0) {
|
|
container.innerHTML = `
|
|
<p>Keine Filterregeln für dieses Konto.</p>
|
|
<button onclick="openNewFilter()">Neue Regel erstellen</button>
|
|
`;
|
|
return;
|
|
}
|
|
let html = '<button onclick="openNewFilter()" style="margin-bottom:1rem">Neue Regel</button><table><thead><tr><th>Prio</th><th>Name</th><th>Ordner</th><th>Bedingungen</th><th>Aktionen</th><th></th></tr></thead><tbody>';
|
|
for (const f of filters) {
|
|
const conds = f.conditions.map(c => {
|
|
const fieldLabel = FIELDS.find(x => x.value === c.field)?.label || c.field;
|
|
const matchLabel = MATCH_TYPES.find(x => x.value === c.match_type)?.label || c.match_type;
|
|
return `${c.negate ? 'NICHT ' : ''}${fieldLabel} ${matchLabel} "${c.value}"`;
|
|
}).join('<br>');
|
|
const acts = f.actions.map(a => {
|
|
const at = ACTION_TYPES.find(x => x.value === a.action_type);
|
|
return `${at?.label || a.action_type}${a.parameter ? ': ' + a.parameter : ''}`;
|
|
}).join('<br>');
|
|
html += `<tr>
|
|
<td>${f.priority}</td>
|
|
<td>${f.name}${f.stop_processing ? ' <small>(stop)</small>' : ''}</td>
|
|
<td>${f.source_folder}</td>
|
|
<td><small>${conds}</small></td>
|
|
<td><small>${acts}</small></td>
|
|
<td>
|
|
<div role="group">
|
|
<button class="outline small" onclick='editFilter(${JSON.stringify(f)})'>Bearbeiten</button>
|
|
<button class="outline small contrast" onclick="deleteFilter(${f.id})">Löschen</button>
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
html += '</tbody></table>';
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function addCondition(data = null) {
|
|
const list = document.getElementById('conditions-list');
|
|
const idx = list.children.length;
|
|
const div = document.createElement('div');
|
|
div.className = 'grid';
|
|
div.style.marginBottom = '0.5rem';
|
|
div.innerHTML = `
|
|
<select name="cond_field_${idx}">
|
|
${FIELDS.map(f => `<option value="${f.value}" ${data?.field === f.value ? 'selected' : ''}>${f.label}</option>`).join('')}
|
|
</select>
|
|
<select name="cond_match_${idx}">
|
|
${MATCH_TYPES.map(m => `<option value="${m.value}" ${data?.match_type === m.value ? 'selected' : ''}>${m.label}</option>`).join('')}
|
|
</select>
|
|
<input type="text" name="cond_value_${idx}" value="${data?.value || ''}" placeholder="Wert" required>
|
|
<label style="white-space:nowrap"><input type="checkbox" name="cond_negate_${idx}" ${data?.negate ? 'checked' : ''}> NOT</label>
|
|
<button type="button" class="outline small contrast" onclick="this.parentElement.remove()">X</button>
|
|
`;
|
|
list.appendChild(div);
|
|
}
|
|
|
|
function addAction(data = null) {
|
|
const list = document.getElementById('actions-list');
|
|
const idx = list.children.length;
|
|
const div = document.createElement('div');
|
|
div.className = 'grid';
|
|
div.style.marginBottom = '0.5rem';
|
|
const selectedType = ACTION_TYPES.find(a => a.value === data?.action_type);
|
|
div.innerHTML = `
|
|
<select name="act_type_${idx}" onchange="toggleActionParam(this)">
|
|
${ACTION_TYPES.map(a => `<option value="${a.value}" data-needs-param="${a.needsParam}" data-param-label="${a.paramLabel || ''}" ${data?.action_type === a.value ? 'selected' : ''}>${a.label}</option>`).join('')}
|
|
</select>
|
|
<input type="text" name="act_param_${idx}" value="${data?.parameter || ''}" placeholder="${selectedType?.paramLabel || 'Parameter'}" ${selectedType?.needsParam === false && !data?.parameter ? 'style=display:none' : ''}>
|
|
<button type="button" class="outline small contrast" onclick="this.parentElement.remove()">X</button>
|
|
`;
|
|
list.appendChild(div);
|
|
}
|
|
|
|
function toggleActionParam(select) {
|
|
const opt = select.options[select.selectedIndex];
|
|
const input = select.parentElement.querySelector('input[type=text]');
|
|
if (opt.dataset.needsParam === 'true') {
|
|
input.style.display = '';
|
|
input.placeholder = opt.dataset.paramLabel;
|
|
} else {
|
|
input.style.display = 'none';
|
|
input.value = '';
|
|
}
|
|
}
|
|
|
|
function openNewFilter() {
|
|
document.getElementById('dialog-title').textContent = 'Neue Filterregel';
|
|
document.getElementById('filter-id').value = '';
|
|
document.getElementById('filter-name').value = '';
|
|
document.getElementById('filter-priority').value = '100';
|
|
document.getElementById('filter-source-folder').value = 'INBOX';
|
|
document.getElementById('filter-stop-processing').checked = false;
|
|
document.getElementById('conditions-list').innerHTML = '';
|
|
document.getElementById('actions-list').innerHTML = '';
|
|
addCondition();
|
|
addAction();
|
|
document.getElementById('filter-dialog').showModal();
|
|
}
|
|
|
|
function editFilter(f) {
|
|
document.getElementById('dialog-title').textContent = 'Regel bearbeiten';
|
|
document.getElementById('filter-id').value = f.id;
|
|
document.getElementById('filter-name').value = f.name;
|
|
document.getElementById('filter-priority').value = f.priority;
|
|
document.getElementById('filter-source-folder').value = f.source_folder;
|
|
document.getElementById('filter-stop-processing').checked = f.stop_processing;
|
|
document.getElementById('conditions-list').innerHTML = '';
|
|
document.getElementById('actions-list').innerHTML = '';
|
|
for (const c of f.conditions) addCondition(c);
|
|
for (const a of f.actions) addAction(a);
|
|
document.getElementById('filter-dialog').showModal();
|
|
}
|
|
|
|
function collectFormData() {
|
|
const accountId = document.getElementById('account-select').value;
|
|
const conditions = [];
|
|
const condRows = document.getElementById('conditions-list').children;
|
|
for (let i = 0; i < condRows.length; i++) {
|
|
const row = condRows[i];
|
|
const selects = row.querySelectorAll('select');
|
|
const input = row.querySelector('input[type=text]');
|
|
const checkbox = row.querySelector('input[type=checkbox]');
|
|
conditions.push({
|
|
field: selects[0].value,
|
|
match_type: selects[1].value,
|
|
value: input.value,
|
|
negate: checkbox.checked,
|
|
});
|
|
}
|
|
const actions = [];
|
|
const actRows = document.getElementById('actions-list').children;
|
|
for (let i = 0; i < actRows.length; i++) {
|
|
const row = actRows[i];
|
|
const select = row.querySelector('select');
|
|
const input = row.querySelector('input[type=text]');
|
|
actions.push({
|
|
action_type: select.value,
|
|
parameter: input.value || null,
|
|
});
|
|
}
|
|
return {
|
|
account_id: parseInt(accountId),
|
|
name: document.getElementById('filter-name').value,
|
|
priority: parseInt(document.getElementById('filter-priority').value),
|
|
source_folder: document.getElementById('filter-source-folder').value,
|
|
stop_processing: document.getElementById('filter-stop-processing').checked,
|
|
enabled: true,
|
|
conditions,
|
|
actions,
|
|
};
|
|
}
|
|
|
|
document.getElementById('filter-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const data = collectFormData();
|
|
const filterId = document.getElementById('filter-id').value;
|
|
|
|
let resp;
|
|
if (filterId) {
|
|
resp = await fetch(`/api/filters/${filterId}`, {
|
|
method: 'PUT',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data),
|
|
});
|
|
} else {
|
|
resp = await fetch('/api/filters/', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
if (resp.ok) {
|
|
document.getElementById('filter-dialog').close();
|
|
loadFilters();
|
|
} else {
|
|
const err = await resp.json();
|
|
alert('Fehler: ' + JSON.stringify(err));
|
|
}
|
|
});
|
|
|
|
async function deleteFilter(id) {
|
|
if (!confirm('Filterregel wirklich löschen?')) return;
|
|
await fetch(`/api/filters/${id}`, {method: 'DELETE'});
|
|
loadFilters();
|
|
}
|
|
|
|
// Auto-load if account is pre-selected
|
|
if (document.getElementById('account-select').value) loadFilters();
|
|
</script>
|
|
{% endblock %}
|