adding editing
Deploy / deploy (push) Successful in 8s

This commit is contained in:
2026-05-06 18:23:36 +02:00
parent 3d3895e47b
commit 08df9bfe5c
3 changed files with 74 additions and 12 deletions
+50 -12
View File
@@ -249,7 +249,7 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius
<div class="page-section" id="page-rules"> <div class="page-section" id="page-rules">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0"><i class="bi bi-sliders me-2"></i>Alert Rules</h5> <h5 class="mb-0"><i class="bi bi-sliders me-2"></i>Alert Rules</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#ruleModal"><i class="bi bi-plus-lg"></i> Add Rule</button> <button class="btn btn-primary btn-sm" onclick="resetRuleForm();new bootstrap.Modal(document.getElementById('ruleModal')).show()"><i class="bi bi-plus-lg"></i> Add Rule</button>
</div> </div>
<div class="card"> <div class="card">
<div class="card-body p-0"> <div class="card-body p-0">
@@ -386,19 +386,20 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<form id="ruleForm"> <form id="ruleForm">
<div class="modal-header"><h5 class="modal-title">Add Alert Rule</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-header"><h5 class="modal-title" id="ruleModalTitle">Add Alert Rule</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body"> <div class="modal-body">
<input type="hidden" name="id" id="ruleFormId" value="">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Name</label> <label class="form-label">Name</label>
<input type="text" class="form-control" name="name" required placeholder="e.g. PHP Error"> <input type="text" class="form-control" name="name" id="ruleFormName" required placeholder="e.g. SSH Login">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Pattern <small class="text-secondary">(PHP regex)</small></label> <label class="form-label">Pattern <small class="text-secondary">(PHP regex)</small></label>
<input type="text" class="form-control" name="pattern" required placeholder="/error/i"> <input type="text" class="form-control" name="pattern" id="ruleFormPattern" required placeholder="/sshd.*Accepted/i">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Severity</label> <label class="form-label">Severity</label>
<select class="form-select" name="severity" required> <select class="form-select" name="severity" id="ruleFormSeverity" required>
<option value="emergency">Emergency</option> <option value="emergency">Emergency</option>
<option value="critical_high">Critical High</option> <option value="critical_high">Critical High</option>
<option value="critical">Critical</option> <option value="critical">Critical</option>
@@ -414,11 +415,15 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Rate Limit <small class="text-secondary">(seconds, empty = no limit)</small></label> <label class="form-label">Rate Limit <small class="text-secondary">(seconds, empty = no limit)</small></label>
<input type="number" class="form-control" name="rate_limit_seconds" placeholder="60"> <input type="number" class="form-control" name="rate_limit_seconds" id="ruleFormRateLimit" placeholder="60">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="active" id="ruleFormActive" checked>
<label class="form-check-label" for="ruleFormActive">Active</label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary">Add Rule</button> <button type="submit" class="btn btn-primary" id="ruleFormSubmit">Add Rule</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div> </div>
</form> </form>
@@ -790,7 +795,7 @@ async function loadRules() {
<td>${severityBadge(r.severity)}</td> <td>${severityBadge(r.severity)}</td>
<td>${r.rate_limit_seconds ? r.rate_limit_seconds + 's' : '—'}</td> <td>${r.rate_limit_seconds ? r.rate_limit_seconds + 's' : '—'}</td>
<td>${r.active ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'}</td> <td>${r.active ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'}</td>
<td><button class="btn btn-outline-danger btn-sm py-0" onclick="deleteRule(${r.id})"><i class="bi bi-trash"></i></button></td> <td><button class="btn btn-outline-primary btn-sm py-0 me-1" onclick="editRule(${r.id})"><i class="bi bi-pencil"></i></button><button class="btn btn-outline-danger btn-sm py-0" onclick="deleteRule(${r.id})"><i class="bi bi-trash"></i></button></td>
</tr>`).join(''); </tr>`).join('');
} }
} catch (e) { console.error('rules error', e); } } catch (e) { console.error('rules error', e); }
@@ -805,18 +810,51 @@ async function deleteRule(id) {
} catch (e) { toast('Delete failed', 'danger'); } } catch (e) { toast('Delete failed', 'danger'); }
} }
function editRule(id) {
const r = state.rules.find(x => x.id === id);
if (!r) return;
document.getElementById('ruleModalTitle').textContent = 'Edit Rule';
document.getElementById('ruleFormSubmit').textContent = 'Update Rule';
document.getElementById('ruleFormId').value = r.id;
document.getElementById('ruleFormName').value = r.name;
document.getElementById('ruleFormPattern').value = r.pattern;
document.getElementById('ruleFormSeverity').value = r.severity;
document.getElementById('ruleFormRateLimit').value = r.rate_limit_seconds || '';
document.getElementById('ruleFormActive').checked = r.active;
new bootstrap.Modal(document.getElementById('ruleModal')).show();
}
function resetRuleForm() {
document.getElementById('ruleModalTitle').textContent = 'Add Alert Rule';
document.getElementById('ruleFormSubmit').textContent = 'Add Rule';
document.getElementById('ruleFormId').value = '';
document.getElementById('ruleForm').reset();
document.getElementById('ruleFormActive').checked = true;
}
document.getElementById('ruleModal').addEventListener('hidden.bs.modal', resetRuleForm);
document.getElementById('ruleForm').addEventListener('submit', async e => { document.getElementById('ruleForm').addEventListener('submit', async e => {
e.preventDefault(); e.preventDefault();
const data = Object.fromEntries(new FormData(e.target)); const data = Object.fromEntries(new FormData(e.target));
if (data.rate_limit_seconds) data.rate_limit_seconds = parseInt(data.rate_limit_seconds); if (data.rate_limit_seconds) data.rate_limit_seconds = parseInt(data.rate_limit_seconds);
else data.rate_limit_seconds = null; else data.rate_limit_seconds = null;
data.active = !!data.active;
const id = data.id;
delete data.id;
try { try {
await api('/rules', { method: 'POST', body: JSON.stringify(data) }); if (id) {
toast('Rule added'); await api('/rules/' + id, { method: 'PUT', body: JSON.stringify(data) });
toast('Rule updated');
} else {
await api('/rules', { method: 'POST', body: JSON.stringify(data) });
toast('Rule added');
}
bootstrap.Modal.getInstance(document.getElementById('ruleModal')).hide(); bootstrap.Modal.getInstance(document.getElementById('ruleModal')).hide();
e.target.reset();
loadRules(); loadRules();
} catch (err) { toast('Failed to add rule', 'danger'); } } catch (err) { toast('Failed to save rule', 'danger'); }
}); });
// --- SETTINGS --- // --- SETTINGS ---
+15
View File
@@ -65,6 +65,8 @@ class Router
$path === '/rules' && $method === 'POST' => $this->createRule(), $path === '/rules' && $method === 'POST' => $this->createRule(),
preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'DELETE' preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'DELETE'
=> $this->deleteEntity('rule', (int) $m[1]), => $this->deleteEntity('rule', (int) $m[1]),
preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'PUT'
=> $this->updateRule((int) $m[1]),
$path === '/alerts' && $method === 'GET' => $this->getAlerts(), $path === '/alerts' && $method === 'GET' => $this->getAlerts(),
$path === '/alerts/search' && $method === 'GET' => $this->searchAlerts(), $path === '/alerts/search' && $method === 'GET' => $this->searchAlerts(),
@@ -185,6 +187,19 @@ class Router
return ['status' => 'deleted', 'id' => $id]; return ['status' => 'deleted', 'id' => $id];
} }
private function updateRule(int $id): mixed
{
$body = json_decode(file_get_contents('php://input'), true);
return $this->repo->updateRule(
id: $id,
name: $body['name'],
pattern: $body['pattern'],
severity: $body['severity'] ?? 'warning',
rateLimitSeconds: $body['rate_limit_seconds'] ?? null,
active: $body['active'] ?? true,
);
}
private function getAlerts(): mixed private function getAlerts(): mixed
{ {
$limit = (int) ($_GET['limit'] ?? 100); $limit = (int) ($_GET['limit'] ?? 100);
+9
View File
@@ -82,6 +82,15 @@ class Repository
$this->db->pdo()->prepare("DELETE FROM rules WHERE id = ?")->execute([$id]); $this->db->pdo()->prepare("DELETE FROM rules WHERE id = ?")->execute([$id]);
} }
public function updateRule(int $id, string $name, string $pattern, string $severity, ?int $rateLimitSeconds = null, bool $active = true): Rule
{
$stmt = $this->db->pdo()->prepare(
"UPDATE rules SET name = ?, pattern = ?, severity = ?, rate_limit_seconds = ?, active = ? WHERE id = ?"
);
$stmt->execute([$name, $pattern, $severity, $rateLimitSeconds, (int) $active, $id]);
return $this->getRule($id);
}
// --- Alerts --- // --- Alerts ---
public function createAlert(int $ruleId, string $ruleName, string $severity, string $message, string $rawLine, ?int $sourceId = null, ?string $sourceName = null): Alert public function createAlert(int $ruleId, string $ruleName, string $severity, string $message, string $rawLine, ?int $sourceId = null, ?string $sourceName = null): Alert