letzter stand

This commit is contained in:
2025-12-16 20:27:37 +01:00
parent 27a8240d3f
commit ffcfce2b31
4 changed files with 256 additions and 28 deletions

View File

@@ -21,6 +21,7 @@
};
let editingId = null;
let exclusionWindows = [];
const form = document.getElementById('automationForm');
const nameInput = document.getElementById('nameInput');
@@ -34,6 +35,10 @@
const jitterInput = document.getElementById('jitterInput');
const startAtInput = document.getElementById('startAtInput');
const runUntilInput = document.getElementById('runUntilInput');
const excludeStartInput = document.getElementById('excludeStartInput');
const excludeEndInput = document.getElementById('excludeEndInput');
const addExclusionBtn = document.getElementById('addExclusionBtn');
const exclusionList = document.getElementById('exclusionList');
const activeToggle = document.getElementById('activeToggle');
const formStatus = document.getElementById('formStatus');
const formModeLabel = document.getElementById('formModeLabel');
@@ -60,6 +65,7 @@
const listStatus = document.getElementById('listStatus');
const filterName = document.getElementById('filterName');
const filterNext = document.getElementById('filterNext');
const filterRunUntil = document.getElementById('filterRunUntil');
const filterLast = document.getElementById('filterLast');
const filterStatus = document.getElementById('filterStatus');
const filterRuns = document.getElementById('filterRuns');
@@ -342,7 +348,8 @@
jitter_minutes: Math.max(0, parseInt(jitterInput.value, 10) || 0),
start_at: parseDateInput(startAtInput.value),
run_until: parseDateInput(runUntilInput.value),
active: activeToggle.checked
active: activeToggle.checked,
exclusion_windows: exclusionWindows.map((win) => ({ start: win.start, end: win.end }))
};
if (type === 'email') {
@@ -389,6 +396,46 @@
};
}
function renderExclusionChips() {
if (!exclusionList) return;
if (!exclusionWindows.length) {
exclusionList.innerHTML = '<span class="placeholder-hint">Keine Ausschlusszeiten gesetzt.</span>';
return;
}
exclusionList.innerHTML = exclusionWindows.map((win, idx) => `
<span class="exclusion-chip">
${win.start} ${win.end}
<button type="button" aria-label="Entfernen" data-remove-exclusion="${idx}">×</button>
</span>
`).join('');
exclusionList.querySelectorAll('[data-remove-exclusion]').forEach((btn) => {
btn.addEventListener('click', () => {
const index = Number(btn.getAttribute('data-remove-exclusion'));
exclusionWindows.splice(index, 1);
renderExclusionChips();
});
});
}
function addExclusion() {
const start = excludeStartInput?.value || '';
const end = excludeEndInput?.value || '';
if (!start || !end) {
setStatus(formStatus, 'Bitte Start- und Endzeit angeben.', 'error');
return;
}
if (start >= end) {
setStatus(formStatus, 'Endzeit muss nach der Startzeit liegen.', 'error');
return;
}
exclusionWindows.push({ start, end });
exclusionWindows.sort((a, b) => a.start.localeCompare(b.start));
renderExclusionChips();
setStatus(formStatus, '', 'info');
if (excludeStartInput) excludeStartInput.value = '';
if (excludeEndInput) excludeEndInput.value = '';
}
function applyPresetDisabling() {
if (intervalPreset.value === 'custom') {
intervalMinutesInput.disabled = false;
@@ -523,6 +570,8 @@
if (!req) return;
const nextEl = row.querySelector('.next');
if (nextEl) nextEl.textContent = formatRelative(req.next_run_at);
const untilEl = row.querySelector('.until');
if (untilEl) untilEl.textContent = req.run_until ? formatRelative(req.run_until) : '—';
const lastEl = row.querySelector('.last');
if (lastEl) lastEl.textContent = formatRelative(req.last_run_at);
});
@@ -532,7 +581,7 @@
if (!requestTableBody) return;
requestTableBody.innerHTML = '';
if (!state.requests.length) {
requestTableBody.innerHTML = '<tr><td colspan="6">Keine Automationen vorhanden.</td></tr>';
requestTableBody.innerHTML = '<tr><td colspan="7">Keine Automationen vorhanden.</td></tr>';
listInstance = null;
return;
}
@@ -540,6 +589,7 @@
const rows = state.requests.map((req) => {
const isSelected = state.selectedId === req.id;
const nextSort = req.next_run_at ? new Date(req.next_run_at).getTime() : Number.MAX_SAFE_INTEGER;
const untilSort = req.run_until ? new Date(req.run_until).getTime() : Number.MAX_SAFE_INTEGER;
const lastSort = req.last_run_at ? new Date(req.last_run_at).getTime() : -Number.MAX_SAFE_INTEGER;
const runsCount = Array.isArray(req.runs) ? req.runs.length : (req.runs_count || req.runsCount || 0);
const statusBadge = req.last_status === 'success'
@@ -571,6 +621,10 @@
<span class="next" data-sort="${nextSort}">${formatRelative(req.next_run_at)}</span>
<br><small>${formatDateTime(req.next_run_at)}</small>
</td>
<td data-sort="${untilSort}">
<span class="until" data-sort="${untilSort}">${req.run_until ? formatRelative(req.run_until) : '—'}</span>
<br><small>${formatDateTime(req.run_until)}</small>
</td>
<td data-sort="${lastSort}">
<span class="last" data-sort="${lastSort}">${formatRelative(req.last_run_at)}</span>
<br><small>${req.last_status_code || '—'}</small>
@@ -640,6 +694,8 @@
now.setSeconds(0, 0);
startAtInput.value = toDateTimeLocal(now);
runUntilInput.value = '';
exclusionWindows = [];
renderExclusionChips();
formModeLabel.textContent = 'Neue Automation';
setStatus(formStatus, '');
if (emailToInput) emailToInput.value = '';
@@ -700,6 +756,8 @@
jitterInput.value = request.jitter_minutes || 0;
startAtInput.value = toDateTimeLocal(request.start_at);
runUntilInput.value = toDateTimeLocal(request.run_until);
exclusionWindows = Array.isArray(request.exclusion_windows) ? [...request.exclusion_windows] : [];
renderExclusionChips();
applyPresetDisabling();
refreshPreview();
}
@@ -975,6 +1033,7 @@
'url',
'steps',
{ name: 'next', attr: 'data-sort' },
{ name: 'runUntil', attr: 'data-sort' },
{ name: 'last', attr: 'data-sort' },
{ name: 'status', attr: 'data-sort' },
{ name: 'runs', attr: 'data-sort' }
@@ -990,6 +1049,7 @@
if (!listInstance) return;
const searchName = (filterName?.value || '').toLowerCase().trim();
const searchNext = (filterNext?.value || '').toLowerCase().trim();
const searchRunUntil = (filterRunUntil?.value || '').toLowerCase().trim();
const searchLast = (filterLast?.value || '').toLowerCase().trim();
const statusValue = (filterStatus?.value || '').toLowerCase().trim();
const runsMin = filterRuns?.value ? Number(filterRuns.value) : null;
@@ -1005,11 +1065,12 @@
].some((p) => String(p).toLowerCase().includes(searchName));
const matchNext = !searchNext || String(v.next || '').toLowerCase().includes(searchNext);
const matchRunUntil = !searchRunUntil || String(v.runUntil || '').toLowerCase().includes(searchRunUntil);
const matchLast = !searchLast || `${v.last || ''}`.toLowerCase().includes(searchLast);
const matchStatus = !statusValue || String(v.status || '').toLowerCase().includes(statusValue);
const matchRuns = runsMin === null || Number(v.runs || 0) >= runsMin;
return matchName && matchNext && matchLast && matchStatus && matchRuns;
return matchName && matchNext && matchRunUntil && matchLast && matchStatus && matchRuns;
});
updateSortIndicators();
}
@@ -1200,11 +1261,14 @@
el?.addEventListener('input', refreshPreview);
el?.addEventListener('change', refreshPreview);
});
if (addExclusionBtn) {
addExclusionBtn.addEventListener('click', addExclusion);
}
typeSelect?.addEventListener('change', () => {
applyTypeVisibility();
refreshPreview();
});
[filterName, filterNext, filterLast, filterStatus].forEach((el) => {
[filterName, filterNext, filterRunUntil, filterLast, filterStatus].forEach((el) => {
el?.addEventListener('input', applyFilters);
el?.addEventListener('change', applyFilters);
});