Quelltext des aktuellen Standes
<?php $this->layout('layouts/admin', [
'page_title' => 'XDR-Regeln',
'current_admin_page' => 'xdr-rules',
]); ?>
<?php
$rules = $rules ?? [];
$actions = $actions ?? [];
$matchStates = $match_states ?? [];
$providerList = $providers ?? [];
$alertTypes = $alert_types ?? [];
$users = $users ?? [];
$ticketsModule = $tickets_module ?? false;
$view = $this;
$severities = ['critical' => 'Kritisch', 'high' => 'Hoch', 'medium' => 'Mittel', 'low' => 'Niedrig', 'info' => 'Info'];
/** Bedingungen einer Regel als lesbare Aufzählung. */
$bedingungen = static function (array $r) use ($providerList, $alertTypes, $severities, $matchStates): array {
$teile = [];
if (!empty($r['match_provider'])) {
$teile[] = 'Anbieter: ' . ($providerList[$r['match_provider']] ?? $r['match_provider']);
}
if (!empty($r['match_alert_type'])) {
$teile[] = 'Art: ' . ($alertTypes[$r['match_alert_type']] ?? $r['match_alert_type']);
}
if (!empty($r['match_min_severity'])) {
$teile[] = 'ab ' . ($severities[$r['match_min_severity']] ?? $r['match_min_severity']);
}
if (($r['match_state'] ?? 'any') !== 'any') {
$teile[] = $matchStates[$r['match_state']] ?? $r['match_state'];
}
if (!empty($r['match_title'])) {
$teile[] = 'Titel ~ „' . $r['match_title'] . '"';
}
if (!empty($r['match_endpoint'])) {
$teile[] = 'Gerät ~ „' . $r['match_endpoint'] . '"';
}
return $teile ?: ['Alle Warnungen'];
};
?>
<div class="settings-page">
<section class="settings-card">
<div class="settings-card-header">
<span class="settings-card-icon">🧹</span>
<div>
<h2><?= $this->te('Regeln für eingehende Warnungen') ?></h2>
<p class="settings-card-desc">
<?= $this->te('Jede neue Warnung wird der Reihe nach gegen diese Regeln geprüft – die erste zutreffende gewinnt. Bereits vorhandene Warnungen bleiben unberührt.') ?>
</p>
</div>
</div>
<div class="settings-card-body">
<p style="margin-top:0;">
<button type="button" class="btn btn-primary" onclick="xdrRuleOpen()">
<i class="fas fa-plus"></i> <?= $this->te('Regel anlegen') ?>
</button>
<a href="/admin/xdr/settings" class="btn btn-default"><?= $this->te('Zu den Einstellungen') ?></a>
</p>
<?php if (empty($rules)): ?>
<div class="empty-state">
<i class="fas fa-filter"></i>
<p><?= $this->te('Noch keine Regel angelegt. Ohne Regeln werden alle Warnungen unverändert übernommen.') ?></p>
</div>
<?php else: ?>
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th scope="col" style="width:5rem;"><?= $this->te('Folge') ?></th>
<th scope="col"><?= $this->te('Regel') ?></th>
<th scope="col"><?= $this->te('Trifft auf') ?></th>
<th scope="col"><?= $this->te('Aktion') ?></th>
<th scope="col" style="width:9rem;"><?= $this->te('Treffer') ?></th>
<th scope="col" style="width:9rem;"></th>
</tr>
</thead>
<tbody>
<?php foreach ($rules as $r): ?>
<tr<?= empty($r['enabled']) ? ' style="opacity:.55;"' : '' ?>>
<td><?= (int)$r['sort_order'] ?></td>
<td>
<strong><?= $view->e((string)$r['name']) ?></strong><br>
<span class="status-pill <?= !empty($r['enabled']) ? 'status-pill--success' : 'status-pill--neutral' ?>">
<?= !empty($r['enabled']) ? 'Aktiv' : 'Inaktiv' ?>
</span>
</td>
<td style="font-size:.85rem;">
<?= $view->e(implode(' · ', $bedingungen($r))) ?>
</td>
<td>
<?= $view->e($actions[$r['action']] ?? (string)$r['action']) ?>
<?php if ($r['action'] === 'assign' && !empty($r['action_user_name'])): ?>
<br><small><?= $view->e((string)$r['action_user_name']) ?></small>
<?php elseif ($r['action'] === 'ticket' && !$ticketsModule): ?>
<br><small class="text-danger"><?= $this->te('Tickets-Modul ist aus') ?></small>
<?php endif; ?>
</td>
<td>
<?= (int)$r['applied_count'] ?>
<?php if (!empty($r['last_applied_at'])): ?>
<br><small>zuletzt <?= $view->e(date('d.m.Y H:i', strtotime((string)$r['last_applied_at']))) ?></small>
<?php endif; ?>
</td>
<td style="text-align:right; white-space:nowrap;">
<button aria-label="<?= $this->te('Bearbeiten') ?>" type="button" class="btn btn-default btn-sm"
onclick='xdrRuleOpen(<?= json_encode($r, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG) ?>)'>
<i class="fas fa-pen"></i>
</button>
<form method="post" action="/admin/xdr/rules/<?= (int)$r['id'] ?>/delete" style="display:inline;"
onsubmit="return confirm('Regel „<?= $view->e((string)$r['name']) ?>“ wirklich löschen?');">
<input type="hidden" name="_csrf" value="<?= $view->e($view->csrf()) ?>">
<button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-danger btn-sm"><i class="fas fa-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</section>
</div>
<!-- Regel anlegen / bearbeiten -->
<div class="modal-overlay" id="xdrRuleModal" style="display:none;">
<div class="modal-card">
<form method="post" action="/admin/xdr/rules">
<input type="hidden" name="_csrf" value="<?= $view->e($view->csrf()) ?>">
<input type="hidden" name="id" id="xr-id" value="">
<div class="modal-header">
<h3 id="xr-title"><?= $this->te('Regel anlegen') ?></h3>
<button aria-label="<?= $this->te('Schließen') ?>" type="button" class="modal-close" onclick="xdrRuleClose()">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="xr-name"><?= $this->te('Name') ?> <span class="text-danger">*</span></label>
<input type="text" id="xr-name" name="name" class="form-control" maxlength="160" required>
</div>
<div class="form-group">
<label for="xr-order"><?= $this->te('Reihenfolge') ?></label>
<input type="number" id="xr-order" name="sort_order" class="form-control form-control-sm" min="0" max="9999" value="100">
<small class="form-hint"><?= $this->te('Kleinere Zahlen werden zuerst geprüft.') ?></small>
</div>
<label class="settings-toggle" style="margin-bottom:1rem;">
<input type="checkbox" name="enabled" id="xr-enabled" value="1" checked>
<span class="toggle-text"><strong><?= $this->te('Regel aktiv') ?></strong><small><?= $this->te('Inaktive Regeln werden übersprungen.') ?></small></span>
</label>
<hr>
<p style="font-weight:600; margin:.25rem 0 .75rem;"><?= $this->te('Trifft auf') ?></p>
<p class="form-hint" style="margin-top:0;"><?= $this->te('Leere Felder bedeuten „alles". Alle ausgefüllten Bedingungen müssen zutreffen.') ?></p>
<div class="form-group">
<label for="xr-provider"><?= $this->te('Anbieter') ?></label>
<select id="xr-provider" name="match_provider" class="form-control form-control-sm">
<option value=""><?= $this->te('Alle') ?></option>
<?php foreach ($providerList as $k => $lbl): ?>
<option value="<?= $view->e((string)$k) ?>"><?= $view->e((string)$lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="xr-type"><?= $this->te('Art der Warnung') ?></label>
<select id="xr-type" name="match_alert_type" class="form-control form-control-sm">
<option value=""><?= $this->te('Alle') ?></option>
<?php foreach ($alertTypes as $k => $lbl): ?>
<option value="<?= $view->e((string)$k) ?>"><?= $view->e((string)$lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="xr-sev"><?= $this->te('Mindest-Schweregrad') ?></label>
<select id="xr-sev" name="match_min_severity" class="form-control form-control-sm">
<option value=""><?= $this->te('Alle') ?></option>
<?php foreach ($severities as $k => $lbl): ?>
<option value="<?= $k ?>"><?= $lbl ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="xr-state"><?= $this->te('Zuordnung zum Inventar') ?></label>
<select id="xr-state" name="match_state" class="form-control form-control-sm">
<?php foreach ($matchStates as $k => $lbl): ?>
<option value="<?= $view->e((string)$k) ?>"><?= $view->e((string)$lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="xr-title-match"><?= $this->te('Titel enthält') ?></label>
<input type="text" id="xr-title-match" name="match_title" class="form-control form-control-sm" maxlength="255"
placeholder="<?= $this->te('z. B. Test-Signatur') ?>">
<small class="form-hint">
<?= $this->t('Groß-/Kleinschreibung spielt keine Rolle. Mit <code>*</code> (beliebig viele Zeichen) oder <code>?</code> (ein Zeichen) beschreibt das Muster den <em>gesamten</em> Titel.') ?>
</small>
</div>
<div class="form-group">
<label for="xr-endpoint"><?= $this->te('Gerätename enthält') ?></label>
<input type="text" id="xr-endpoint" name="match_endpoint" class="form-control form-control-sm" maxlength="255"
placeholder="<?= $this->te('z. B. LAB-*') ?>">
</div>
<hr>
<p style="font-weight:600; margin:.25rem 0 .75rem;"><?= $this->te('Dann') ?></p>
<div class="form-group">
<label for="xr-action"><?= $this->te('Aktion') ?> <span class="text-danger">*</span></label>
<select id="xr-action" name="action" class="form-control" required onchange="xdrRuleActionChanged()">
<?php foreach ($actions as $k => $lbl): ?>
<option value="<?= $view->e((string)$k) ?>"><?= $view->e((string)$lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" id="xr-user-group" hidden>
<label for="xr-user"><?= $this->te('Zuständige Person') ?> <span class="text-danger">*</span></label>
<select id="xr-user" name="action_user_id" class="form-control form-control-sm">
<option value=""><?= $this->te('– bitte wählen –') ?></option>
<?php foreach ($users as $u): ?>
<option value="<?= (int)$u['id'] ?>">
<?= $view->e(trim((string)($u['display_name'] ?? '')) !== '' ? (string)$u['display_name'] : (string)$u['email']) ?>
</option>
<?php endforeach; ?>
</select>
<small class="form-hint"><?= $this->te('Nur diese Person wird benachrichtigt – unabhängig vom Schweregrad.') ?></small>
</div>
<div class="alert alert-info" id="xr-ticket-hint" hidden style="margin-bottom:0;">
<?php if ($ticketsModule): ?>
<?= $this->t('Das Ticket entsteht beim nächsten Lauf der Aufgabe <code>alert_to_ticket</code> – auch dann, wenn die Schwellwert-Automation für XDR ausgeschaltet ist.') ?>
<?php else: ?>
<?= $this->te('Das Tickets-Modul ist derzeit deaktiviert; diese Aktion bleibt dann wirkungslos.') ?>
<?php endif; ?>
</div>
<div class="alert alert-info" id="xr-suppress-hint" hidden style="margin-bottom:0;">
<?= $this->te('Die Warnung wird sofort als verworfen geführt: keine Benachrichtigung, kein automatisches Ticket. Aufgezeichnet und im Board unter „Verworfen" auffindbar bleibt sie trotzdem.') ?>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" onclick="xdrRuleClose()"><?= $this->te('Abbrechen') ?></button>
<button type="submit" class="btn btn-primary"><?= $this->te('Speichern') ?></button>
</div>
</form>
</div>
</div>
<script>
(function () {
const modal = document.getElementById('xdrRuleModal');
function set(id, value) {
const el = document.getElementById(id);
if (el) { el.value = value === null || value === undefined ? '' : String(value); }
}
window.xdrRuleActionChanged = function () {
const aktion = document.getElementById('xr-action').value;
document.getElementById('xr-user-group').hidden = aktion !== 'assign';
document.getElementById('xr-ticket-hint').hidden = aktion !== 'ticket';
document.getElementById('xr-suppress-hint').hidden = aktion !== 'suppress';
};
window.xdrRuleOpen = function (regel) {
document.getElementById('xr-title').textContent = regel ? 'Regel bearbeiten' : 'Regel anlegen';
set('xr-id', regel ? regel.id : '');
set('xr-name', regel ? regel.name : '');
set('xr-order', regel ? regel.sort_order : 100);
set('xr-provider', regel ? regel.match_provider : '');
set('xr-type', regel ? regel.match_alert_type : '');
set('xr-sev', regel ? regel.match_min_severity : '');
set('xr-state', regel ? (regel.match_state || 'any') : 'any');
set('xr-title-match', regel ? regel.match_title : '');
set('xr-endpoint', regel ? regel.match_endpoint : '');
set('xr-action', regel ? regel.action : 'suppress');
set('xr-user', regel && regel.action_user_id ? regel.action_user_id : '');
document.getElementById('xr-enabled').checked = regel ? Number(regel.enabled) === 1 : true;
window.xdrRuleActionChanged();
modal.style.display = 'flex';
};
window.xdrRuleClose = function () {
modal.style.display = 'none';
};
modal.addEventListener('click', function (e) {
if (e.target === modal) { window.xdrRuleClose(); }
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal.style.display !== 'none') { window.xdrRuleClose(); }
});
})();
</script>