Files
PostTracker/web/automation.js
2025-12-15 12:14:59 +01:00

832 lines
28 KiB
JavaScript

(function () {
const API_URL = (() => {
if (window.API_URL) return window.API_URL;
try {
return `${window.location.origin}/api`;
} catch (error) {
return 'https://fb.srv.medeba-media.de/api';
}
})();
const DEFAULT_INTERVAL_MINUTES = 60;
const state = {
requests: [],
runs: [],
selectedId: null,
loading: false,
saving: false,
running: false
};
let editingId = null;
const form = document.getElementById('automationForm');
const nameInput = document.getElementById('nameInput');
const descriptionInput = document.getElementById('descriptionInput');
const urlInput = document.getElementById('urlInput');
const methodSelect = document.getElementById('methodSelect');
const headersInput = document.getElementById('headersInput');
const bodyInput = document.getElementById('bodyInput');
const intervalPreset = document.getElementById('intervalPreset');
const intervalMinutesInput = document.getElementById('intervalMinutesInput');
const jitterInput = document.getElementById('jitterInput');
const startAtInput = document.getElementById('startAtInput');
const runUntilInput = document.getElementById('runUntilInput');
const activeToggle = document.getElementById('activeToggle');
const formStatus = document.getElementById('formStatus');
const formModeLabel = document.getElementById('formModeLabel');
const formModal = document.getElementById('formModal');
const modalBackdrop = document.getElementById('formModalBackdrop');
const modalCloseBtn = document.getElementById('modalCloseBtn');
const requestTableBody = document.getElementById('requestTableBody');
const listStatus = document.getElementById('listStatus');
const runsList = document.getElementById('runsList');
const runsStatus = document.getElementById('runsStatus');
const heroStats = document.getElementById('heroStats');
const saveBtn = document.getElementById('saveBtn');
const resetFormBtn = document.getElementById('resetFormBtn');
const refreshBtn = document.getElementById('refreshBtn');
const newAutomationBtn = document.getElementById('newAutomationBtn');
const runSelectedBtn = document.getElementById('runSelectedBtn');
const modalTitle = document.getElementById('modalTitle');
const importInput = document.getElementById('importInput');
const importStatus = document.getElementById('importStatus');
const applyImportBtn = document.getElementById('applyImportBtn');
const openImportBtn = document.getElementById('openImportBtn');
const importModal = document.getElementById('importModal');
const importModalBackdrop = document.getElementById('importModalBackdrop');
const importCloseBtn = document.getElementById('importCloseBtn');
const previewUrl = document.getElementById('previewUrl');
const previewHeaders = document.getElementById('previewHeaders');
const previewBody = document.getElementById('previewBody');
const refreshPreviewBtn = document.getElementById('refreshPreviewBtn');
function toDateTimeLocal(value) {
if (!value) return '';
const date = value instanceof Date ? value : new Date(value);
if (Number.isNaN(date.getTime())) return '';
const pad = (num) => String(num).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
function parseDateInput(value) {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date.toISOString();
}
async function apiFetchJSON(path, options = {}) {
const opts = {
credentials: 'include',
...options
};
opts.headers = {
'Content-Type': 'application/json',
...(options.headers || {})
};
const response = await fetch(`${API_URL}${path}`, opts);
if (!response.ok) {
let message = 'Unbekannter Fehler';
try {
const err = await response.json();
message = err.error || message;
} catch (error) {
// ignore
}
throw new Error(message);
}
if (response.status === 204) {
return null;
}
return response.json();
}
function setStatus(target, message, type = 'info') {
if (!target) return;
target.textContent = message || '';
target.classList.remove('error', 'success');
if (type === 'error') target.classList.add('error');
if (type === 'success') target.classList.add('success');
}
function formatDateTime(value) {
if (!value) return '—';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
return date.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
function formatRelative(value) {
if (!value) return '—';
const date = new Date(value);
const now = new Date();
const diffMs = date.getTime() - now.getTime();
const diffMinutes = Math.round(diffMs / 60000);
if (Number.isNaN(diffMinutes)) return '—';
if (Math.abs(diffMinutes) < 1) return 'jetzt';
if (diffMinutes > 0) {
if (diffMinutes < 60) return `in ${diffMinutes} Min`;
const hours = Math.round(diffMinutes / 60);
if (hours < 48) return `in ${hours} Std`;
const days = Math.round(hours / 24);
return `in ${days} Tagen`;
} else {
const abs = Math.abs(diffMinutes);
if (abs < 60) return `vor ${abs} Min`;
const hours = Math.round(abs / 60);
if (hours < 48) return `vor ${hours} Std`;
const days = Math.round(hours / 24);
return `vor ${days} Tagen`;
}
}
function parseHeadersInput(text) {
const trimmed = (text || '').trim();
if (!trimmed) return {};
if (trimmed.startsWith('{')) {
try {
const parsed = JSON.parse(trimmed);
if (parsed && typeof parsed === 'object') {
return parsed;
}
} catch (error) {
// fall back to line parsing
}
}
const headers = {};
trimmed.split('\n').forEach((line) => {
const idx = line.indexOf(':');
if (idx === -1) return;
const key = line.slice(0, idx).trim();
const value = line.slice(idx + 1).trim();
if (key) {
headers[key] = value;
}
});
return headers;
}
function stringifyHeaders(headers) {
if (!headers || typeof headers !== 'object') return '';
return Object.entries(headers)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
}
function buildTemplateContext() {
const now = new Date();
return {
now,
date: now.toISOString().slice(0, 10),
today: now.toISOString().slice(0, 10),
iso: now.toISOString(),
datetime: now.toISOString(),
timestamp: now.getTime(),
year: now.getFullYear(),
month: String(now.getMonth() + 1).padStart(2, '0'),
day: String(now.getDate()).padStart(2, '0'),
hour: String(now.getHours()).padStart(2, '0'),
minute: String(now.getMinutes()).padStart(2, '0'),
weekday: now.toLocaleDateString('de-DE', { weekday: 'long' }),
weekday_short: now.toLocaleDateString('de-DE', { weekday: 'short' })
};
}
function renderTemplate(template, context = {}) {
if (typeof template !== 'string') return '';
const baseDate = context.now instanceof Date ? context.now : new Date();
const uuidFn = typeof crypto !== 'undefined' && crypto.randomUUID
? () => crypto.randomUUID()
: () => Math.random().toString(16).slice(2, 10);
return template.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (_, keyRaw) => {
const key = String(keyRaw || '').trim();
if (!key) return '';
if (key === 'uuid') return uuidFn();
const dateOffsetMatch = key.match(/^date([+-]\d+)?$/);
if (dateOffsetMatch) {
const offset = dateOffsetMatch[1] ? parseInt(dateOffsetMatch[1], 10) : 0;
const shifted = new Date(baseDate);
shifted.setDate(baseDate.getDate() + (Number.isNaN(offset) ? 0 : offset));
return shifted.toISOString().slice(0, 10);
}
switch (key) {
case 'today':
case 'date':
return baseDate.toISOString().slice(0, 10);
case 'iso':
case 'now':
case 'datetime':
return baseDate.toISOString();
case 'timestamp':
return String(baseDate.getTime());
case 'year':
return String(baseDate.getFullYear());
case 'month':
return String(baseDate.getMonth() + 1).padStart(2, '0');
case 'day':
return String(baseDate.getDate()).padStart(2, '0');
case 'hour':
return String(baseDate.getHours()).padStart(2, '0');
case 'minute':
return String(baseDate.getMinutes()).padStart(2, '0');
case 'weekday':
return baseDate.toLocaleDateString('de-DE', { weekday: 'long' });
case 'weekday_short':
return baseDate.toLocaleDateString('de-DE', { weekday: 'short' });
default:
return context[key] !== undefined && context[key] !== null
? String(context[key])
: '';
}
});
}
function renderHeaderTemplates(rawHeaders, context) {
const parsed = parseHeadersInput(rawHeaders);
const rendered = {};
Object.entries(parsed).forEach(([key, value]) => {
const renderedKey = renderTemplate(key, context).trim();
if (!renderedKey) return;
rendered[renderedKey] = renderTemplate(
value === undefined || value === null ? '' : String(value),
context
);
});
return rendered;
}
function buildPayloadFromForm() {
return {
name: nameInput.value.trim(),
description: descriptionInput.value.trim(),
method: methodSelect.value,
url_template: urlInput.value.trim(),
headers: parseHeadersInput(headersInput.value),
body_template: bodyInput.value,
schedule_type: intervalPreset.value !== 'custom' ? intervalPreset.value : undefined,
interval_minutes: intervalPreset.value === 'custom'
? Math.max(5, parseInt(intervalMinutesInput.value, 10) || DEFAULT_INTERVAL_MINUTES)
: undefined,
jitter_minutes: Math.max(0, parseInt(jitterInput.value, 10) || 0),
start_at: parseDateInput(startAtInput.value),
run_until: parseDateInput(runUntilInput.value),
active: activeToggle.checked
};
}
function applyPresetDisabling() {
if (intervalPreset.value === 'custom') {
intervalMinutesInput.disabled = false;
} else {
intervalMinutesInput.disabled = true;
intervalMinutesInput.value = intervalPreset.value === 'daily' ? 1440 : DEFAULT_INTERVAL_MINUTES;
}
}
function refreshPreview() {
if (!previewUrl || !previewHeaders || !previewBody) return;
const context = buildTemplateContext();
const renderedUrl = renderTemplate(urlInput.value || '', context);
const headersObj = renderHeaderTemplates(headersInput.value || '', context);
const renderedBody = renderTemplate(bodyInput.value || '', context);
previewUrl.textContent = renderedUrl || '—';
previewHeaders.textContent = Object.keys(headersObj).length
? stringifyHeaders(headersObj)
: '—';
previewBody.textContent = renderedBody || '—';
}
async function loadRequests() {
setStatus(listStatus, 'Lade Automationen...');
state.loading = true;
try {
const data = await apiFetchJSON('/automation/requests');
state.requests = Array.isArray(data) ? data : [];
renderRequests();
renderHero();
if (state.selectedId) {
const stillExists = state.requests.some((req) => req.id === state.selectedId);
if (!stillExists) {
state.selectedId = null;
state.runs = [];
renderRuns();
}
}
if (!state.selectedId && state.requests.length) {
selectRequest(state.requests[0].id, { focusForm: false });
}
setStatus(listStatus, state.requests.length ? '' : 'Noch keine Automationen angelegt.');
} catch (error) {
console.error(error);
setStatus(listStatus, error.message || 'Automationen konnten nicht geladen werden', 'error');
} finally {
state.loading = false;
}
}
function renderHero() {
if (!heroStats) return;
const active = state.requests.filter((req) => req.active);
const nextRun = active
.filter((req) => req.next_run_at)
.sort((a, b) => new Date(a.next_run_at) - new Date(b.next_run_at))[0];
const lastRun = [...state.requests]
.filter((req) => req.last_run_at)
.sort((a, b) => new Date(b.last_run_at) - new Date(a.last_run_at))[0];
heroStats.innerHTML = `
<div class="stat">
<div class="stat-label">Aktive Automationen</div>
<div class="stat-value">${active.length}/${state.requests.length}</div>
</div>
<div class="stat">
<div class="stat-label">Nächster Lauf</div>
<div class="stat-value">${nextRun ? formatRelative(nextRun.next_run_at) : '—'}</div>
</div>
<div class="stat">
<div class="stat-label">Letzter Status</div>
<div class="stat-value">${lastRun ? (lastRun.last_status || '—') : '—'}</div>
</div>
`;
}
function renderRequests() {
if (!requestTableBody) return;
requestTableBody.innerHTML = '';
const sorted = [...state.requests].sort((a, b) => {
const aNext = a.next_run_at ? new Date(a.next_run_at).getTime() : Infinity;
const bNext = b.next_run_at ? new Date(b.next_run_at).getTime() : Infinity;
return aNext - bNext;
});
if (!sorted.length) {
requestTableBody.innerHTML = '<tr><td colspan="5">Keine Automationen vorhanden.</td></tr>';
return;
}
const rows = sorted.map((req) => {
const isSelected = state.selectedId === req.id;
const statusBadge = req.last_status === 'success'
? '<span class="badge success">OK</span>'
: req.last_status === 'error'
? '<span class="badge error">Fehler</span>'
: '<span class="badge">—</span>';
return `
<tr data-id="${req.id}" class="${isSelected ? 'is-selected' : ''}">
<td>
<div class="row-title">${req.name}</div>
<div class="row-sub">${req.method || 'GET'} · ${req.url_template}</div>
</td>
<td>${formatRelative(req.next_run_at)}<br><small>${formatDateTime(req.next_run_at)}</small></td>
<td>${formatRelative(req.last_run_at)}<br><small>${req.last_status_code || '—'}</small></td>
<td>${statusBadge}</td>
<td>
<div class="row-actions">
<button class="secondary-btn" data-action="edit" data-id="${req.id}" type="button">Bearbeiten</button>
<button class="ghost-btn" data-action="run" data-id="${req.id}" type="button">Run</button>
<button class="ghost-btn" data-action="toggle" data-id="${req.id}" type="button">${req.active ? 'Pause' : 'Aktivieren'}</button>
<button class="ghost-btn" data-action="delete" data-id="${req.id}" type="button">Löschen</button>
</div>
</td>
</tr>
`;
});
requestTableBody.innerHTML = rows.join('');
if (runSelectedBtn) {
runSelectedBtn.disabled = !state.selectedId;
}
}
function renderRuns() {
if (!runsList) return;
runsList.innerHTML = '';
if (!state.runs.length) {
runsList.innerHTML = '<li class="run-item">Noch keine Läufe.</li>';
return;
}
runsList.innerHTML = state.runs.map((run) => {
const badge = run.status === 'success'
? '<span class="badge success">OK</span>'
: run.status === 'error'
? '<span class="badge error">Fehler</span>'
: '<span class="badge">Offen</span>';
return `
<li class="run-item">
<div class="run-top">
<div class="run-meta">
${badge}
<span>${formatDateTime(run.started_at)}</span>
<span>Code: ${run.status_code ?? '—'}</span>
<span>Dauer: ${run.duration_ms ? `${run.duration_ms} ms` : '—'}</span>
</div>
</div>
${run.error ? `<div class="run-body">Fehler: ${run.error}</div>` : ''}
${run.response_body ? `<div class="run-body">${run.response_body}</div>` : ''}
</li>
`;
}).join('');
}
function resetForm() {
form.reset();
editingId = null;
intervalPreset.value = 'hourly';
intervalMinutesInput.value = DEFAULT_INTERVAL_MINUTES;
applyPresetDisabling();
const now = new Date();
now.setMinutes(now.getMinutes() + 5);
now.setSeconds(0, 0);
startAtInput.value = toDateTimeLocal(now);
runUntilInput.value = '';
formModeLabel.textContent = 'Neue Automation';
setStatus(formStatus, '');
}
function fillForm(request) {
if (!request) return;
editingId = request.id;
formModeLabel.textContent = `Automation bearbeiten`;
nameInput.value = request.name || '';
descriptionInput.value = request.description || '';
urlInput.value = request.url_template || '';
methodSelect.value = request.method || 'GET';
headersInput.value = stringifyHeaders(request.headers);
bodyInput.value = request.body_template || '';
activeToggle.checked = !!request.active;
if (request.interval_minutes === 60) {
intervalPreset.value = 'hourly';
} else if (request.interval_minutes === 1440) {
intervalPreset.value = 'daily';
} else {
intervalPreset.value = 'custom';
}
intervalMinutesInput.value = request.interval_minutes || DEFAULT_INTERVAL_MINUTES;
jitterInput.value = request.jitter_minutes || 0;
startAtInput.value = toDateTimeLocal(request.start_at);
runUntilInput.value = toDateTimeLocal(request.run_until);
applyPresetDisabling();
refreshPreview();
}
async function handleSubmit(event) {
event.preventDefault();
if (state.saving) return;
const payload = buildPayloadFromForm();
if (!payload.name || !payload.url_template) {
setStatus(formStatus, 'Name und URL sind Pflichtfelder.', 'error');
return;
}
state.saving = true;
setStatus(formStatus, 'Speichere...');
saveBtn.disabled = true;
try {
if (editingId) {
await apiFetchJSON(`/automation/requests/${editingId}`, {
method: 'PUT',
body: JSON.stringify(payload)
});
} else {
await apiFetchJSON('/automation/requests', {
method: 'POST',
body: JSON.stringify(payload)
});
}
setStatus(formStatus, 'Gespeichert.', 'success');
closeFormModal();
await loadRequests();
} catch (error) {
console.error(error);
setStatus(formStatus, error.message || 'Konnte nicht speichern', 'error');
} finally {
state.saving = false;
saveBtn.disabled = false;
}
}
async function loadRuns(requestId) {
if (!requestId) {
state.runs = [];
renderRuns();
return;
}
setStatus(runsStatus, 'Lade Runs...');
try {
const data = await apiFetchJSON(`/automation/requests/${requestId}/runs?limit=50`);
state.runs = Array.isArray(data) ? data : [];
renderRuns();
setStatus(runsStatus, state.runs.length ? '' : 'Keine Läufe vorhanden.');
} catch (error) {
console.error(error);
setStatus(runsStatus, error.message || 'Runs konnten nicht geladen werden', 'error');
}
}
function selectRequest(id, options = {}) {
state.selectedId = id;
const request = state.requests.find((item) => item.id === id);
if (!request) {
state.runs = [];
renderRuns();
return;
}
renderRequests();
loadRuns(id);
if (options.focusForm) {
nameInput.focus();
}
}
async function runAutomation(id) {
if (!id) {
setStatus(formStatus, 'Keine Automation ausgewählt.', 'error');
return;
}
if (state.running) return;
state.running = true;
setStatus(formStatus, 'Starte manuellen Run...');
try {
await apiFetchJSON(`/automation/requests/${id}/run`, { method: 'POST' });
await loadRequests();
await loadRuns(id);
setStatus(formStatus, 'Run gestartet.', 'success');
} catch (error) {
console.error(error);
setStatus(formStatus, error.message || 'Run fehlgeschlagen', 'error');
} finally {
state.running = false;
}
}
async function toggleActive(id) {
const request = state.requests.find((item) => item.id === id);
if (!request) return;
try {
await apiFetchJSON(`/automation/requests/${id}`, {
method: 'PUT',
body: JSON.stringify({ active: !request.active })
});
await loadRequests();
} catch (error) {
console.error(error);
setStatus(listStatus, error.message || 'Konnte Status nicht ändern', 'error');
}
}
async function deleteAutomation(id) {
const request = state.requests.find((item) => item.id === id);
if (!request) return;
if (!confirm(`Automation "${request.name}" wirklich löschen?`)) {
return;
}
try {
await apiFetchJSON(`/automation/requests/${id}`, { method: 'DELETE' });
if (state.selectedId === id) {
state.selectedId = null;
state.runs = [];
renderRuns();
}
await loadRequests();
} catch (error) {
console.error(error);
setStatus(listStatus, error.message || 'Konnte nicht löschen', 'error');
}
}
function parseCurlTemplate(text) {
const result = {};
const urlMatch = text.match(/curl\s+(['"]?)([^'"\s]+)\1/);
if (urlMatch) result.url = urlMatch[2];
const methodMatch = text.match(/-X\s+([A-Z]+)/i) || text.match(/--request\s+([A-Z]+)/i);
if (methodMatch) result.method = methodMatch[1].toUpperCase();
const headers = {};
const headerRegex = /-H\s+(?:(?:"([^"]+)")|(?:'([^']+)'))/gi;
let headerMatch;
while ((headerMatch = headerRegex.exec(text)) !== null) {
const raw = headerMatch[1] || headerMatch[2] || '';
const idx = raw.indexOf(':');
if (idx > -1) {
const key = raw.slice(0, idx).trim();
const value = raw.slice(idx + 1).trim();
if (key) headers[key] = value;
}
}
if (Object.keys(headers).length) result.headers = headers;
const dataMatch = text.match(/--data(?:-raw)?\s+(?:"([^"]*)"|'([^']*)')/i);
if (dataMatch) {
result.body = dataMatch[1] || dataMatch[2] || '';
}
return result;
}
function parseFetchTemplate(text) {
const result = {};
const urlMatch = text.match(/fetch\(\s*['"]([^'"]+)['"]/i);
if (urlMatch) result.url = urlMatch[1];
const optionsMatch = text.match(/fetch\(\s*['"][^'"]+['"]\s*,\s*({[\s\S]*?})\s*\)/i);
if (optionsMatch) {
try {
let optionsText = optionsMatch[1]
.replace(/,\s*\}\s*$/, '}')
.replace(/,\s*\]/g, ']');
const options = JSON.parse(optionsText);
if (options.method) result.method = options.method.toUpperCase();
if (options.headers && typeof options.headers === 'object') result.headers = options.headers;
if (options.body) result.body = options.body;
} catch (error) {
// ignore parse errors
}
}
return result;
}
function parsePowerShellTemplate(text) {
const result = {};
const urlMatch = text.match(/-Uri\s+['"]([^'"]+)['"]/i);
if (urlMatch) result.url = urlMatch[1];
const methodMatch = text.match(/-Method\s+['"]?([A-Z]+)['"]?/i);
if (methodMatch) result.method = methodMatch[1].toUpperCase();
const headersMatch = text.match(/-Headers\s+@?\{([\s\S]*?)\}/i);
if (headersMatch) {
const headersText = headersMatch[1];
const headers = {};
headersText.split(';').forEach((pair) => {
const idx = pair.indexOf('=');
if (idx === -1) return;
const key = pair.slice(0, idx).replace(/['\s]/g, '').trim();
const value = pair.slice(idx + 1).replace(/['\s]/g, '').trim();
if (key) headers[key] = value;
});
if (Object.keys(headers).length) result.headers = headers;
}
const bodyMatch = text.match(/-Body\s+['"]([\s\S]*?)['"]/i);
if (bodyMatch) result.body = bodyMatch[1];
return result;
}
function parseTemplate(raw) {
if (!raw) return null;
const text = raw.trim();
if (text.startsWith('curl')) return parseCurlTemplate(text);
if (text.includes('fetch(')) return parseFetchTemplate(text);
if (/Invoke-WebRequest|Invoke-RestMethod/i.test(text)) return parsePowerShellTemplate(text);
return null;
}
function applyImport() {
if (!importInput) return;
const raw = importInput.value;
if (!raw || !raw.trim()) {
setStatus(importStatus, 'Keine Vorlage eingegeben.', 'error');
return;
}
const parsed = parseTemplate(raw);
if (!parsed || (!parsed.url && !parsed.method && !parsed.headers && !parsed.body)) {
setStatus(importStatus, 'Konnte die Vorlage nicht erkennen.', 'error');
return;
}
// Neues Formular öffnen und mit der Vorlage befüllen
openFormModal('create');
if (parsed.url) urlInput.value = parsed.url;
if (parsed.method) methodSelect.value = parsed.method.toUpperCase();
if (parsed.headers) headersInput.value = stringifyHeaders(parsed.headers);
if (parsed.body) bodyInput.value = parsed.body;
setStatus(importStatus, 'Vorlage übernommen.', 'success');
setStatus(formStatus, 'Vorlage importiert. Prüfen & speichern.', 'success');
refreshPreview();
closeImportModal();
}
function getSelectedRequest() {
if (!state.selectedId) return null;
return state.requests.find((item) => item.id === state.selectedId) || null;
}
function openFormModal(mode = 'create', request = null, options = {}) {
const skipReset = !!options.skipReset;
if (!skipReset) {
resetForm();
}
if (mode === 'edit' && request) {
fillForm(request);
formModeLabel.textContent = 'Automation bearbeiten';
modalTitle.textContent = 'Automation bearbeiten';
} else {
formModeLabel.textContent = 'Neue Automation';
modalTitle.textContent = 'Neue Automation';
}
formModal.hidden = false;
setTimeout(() => {
nameInput.focus();
}, 10);
refreshPreview();
}
function closeFormModal() {
formModal.hidden = true;
}
function openImportModal() {
if (!importModal) return;
if (importStatus) setStatus(importStatus, '');
importModal.hidden = false;
setTimeout(() => {
importInput?.focus();
}, 10);
}
function closeImportModal() {
if (!importModal) return;
importModal.hidden = true;
}
function handleTableClick(event) {
const button = event.target.closest('[data-action]');
if (button) {
const { action, id } = button.dataset;
if (!id) return;
switch (action) {
case 'edit':
selectRequest(id);
openFormModal('edit', state.requests.find((item) => item.id === id));
break;
case 'run':
runAutomation(id);
break;
case 'toggle':
toggleActive(id);
break;
case 'delete':
deleteAutomation(id);
break;
default:
break;
}
return;
}
const row = event.target.closest('tr[data-id]');
if (row && row.dataset.id) {
selectRequest(row.dataset.id);
}
}
function init() {
applyPresetDisabling();
resetForm();
loadRequests();
form.addEventListener('submit', handleSubmit);
intervalPreset.addEventListener('change', applyPresetDisabling);
resetFormBtn.addEventListener('click', () => {
resetForm();
nameInput.focus();
});
newAutomationBtn.addEventListener('click', () => openFormModal('create'));
refreshBtn.addEventListener('click', loadRequests);
runSelectedBtn.addEventListener('click', () => runAutomation(state.selectedId));
applyImportBtn?.addEventListener('click', applyImport);
refreshPreviewBtn?.addEventListener('click', refreshPreview);
[urlInput, headersInput, bodyInput].forEach((el) => {
el?.addEventListener('input', refreshPreview);
});
openImportBtn?.addEventListener('click', openImportModal);
importCloseBtn?.addEventListener('click', closeImportModal);
importModalBackdrop?.addEventListener('click', closeImportModal);
requestTableBody.addEventListener('click', handleTableClick);
modalCloseBtn.addEventListener('click', closeFormModal);
modalBackdrop.addEventListener('click', closeFormModal);
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
if (!formModal.hidden) {
closeFormModal();
}
if (importModal && !importModal.hidden) {
closeImportModal();
}
}
});
}
init();
})();