+50
-12
@@ -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="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>
|
||||
<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 class="card">
|
||||
<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-content">
|
||||
<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">
|
||||
<input type="hidden" name="id" id="ruleFormId" value="">
|
||||
<div class="mb-3">
|
||||
<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 class="mb-3">
|
||||
<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 class="mb-3">
|
||||
<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="critical_high">Critical High</option>
|
||||
<option value="critical">Critical</option>
|
||||
@@ -414,11 +415,15 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<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 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>
|
||||
</div>
|
||||
</form>
|
||||
@@ -790,7 +795,7 @@ async function loadRules() {
|
||||
<td>${severityBadge(r.severity)}</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><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('');
|
||||
}
|
||||
} catch (e) { console.error('rules error', e); }
|
||||
@@ -805,18 +810,51 @@ async function deleteRule(id) {
|
||||
} 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 => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.target));
|
||||
if (data.rate_limit_seconds) data.rate_limit_seconds = parseInt(data.rate_limit_seconds);
|
||||
else data.rate_limit_seconds = null;
|
||||
data.active = !!data.active;
|
||||
|
||||
const id = data.id;
|
||||
delete data.id;
|
||||
|
||||
try {
|
||||
await api('/rules', { method: 'POST', body: JSON.stringify(data) });
|
||||
toast('Rule added');
|
||||
if (id) {
|
||||
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();
|
||||
e.target.reset();
|
||||
loadRules();
|
||||
} catch (err) { toast('Failed to add rule', 'danger'); }
|
||||
} catch (err) { toast('Failed to save rule', 'danger'); }
|
||||
});
|
||||
|
||||
// --- SETTINGS ---
|
||||
|
||||
@@ -65,6 +65,8 @@ class Router
|
||||
$path === '/rules' && $method === 'POST' => $this->createRule(),
|
||||
preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'DELETE'
|
||||
=> $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/search' && $method === 'GET' => $this->searchAlerts(),
|
||||
@@ -185,6 +187,19 @@ class Router
|
||||
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
|
||||
{
|
||||
$limit = (int) ($_GET['limit'] ?? 100);
|
||||
|
||||
@@ -82,6 +82,15 @@ class Repository
|
||||
$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 ---
|
||||
|
||||
public function createAlert(int $ruleId, string $ruleName, string $severity, string $message, string $rawLine, ?int $sourceId = null, ?string $sourceName = null): Alert
|
||||
|
||||
Reference in New Issue
Block a user