OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

index.php

12 KB
<?php
$this->layout('layouts/main', [
    'page_title'    => $page_title,
    'current_route' => 'xdr',
    'full_width'    => true,
]);

$alerts    = $alerts ?? [];
$kpis      = $kpis ?? ['open' => 0, 'severe' => 0, 'unmatched' => 0, 'last_sync' => null];
$providers = $providers ?? [];
$filter    = $filter ?? ['provider' => '', 'severity' => '', 'status' => '', 'match' => '', 'q' => ''];
$canManage = $can_manage ?? false;
$page      = $page ?? 1;
$pages     = $pages ?? 1;
$total     = $total ?? 0;
$view      = $this;

$sevPill = [
    'critical' => 'status-pill--danger',
    'high'     => 'status-pill--orange',
    'medium'   => 'status-pill--warning',
    'low'      => 'status-pill--info',
    'info'     => 'status-pill--neutral',
];
$sevLabel = [
    'critical' => 'Kritisch', 'high' => 'Hoch', 'medium' => 'Mittel',
    'low' => 'Niedrig', 'info' => 'Info',
];
$statusPill = [
    'new'           => 'status-pill--info',
    'investigating' => 'status-pill--warning',
    'acknowledged'  => 'status-pill--purple',
    'resolved'      => 'status-pill--success',
    'dismissed'     => 'status-pill--neutral',
];
$statusLabel = [
    'new' => 'Neu', 'investigating' => 'In Bearbeitung', 'acknowledged' => 'Quittiert',
    'resolved' => 'Erledigt', 'dismissed' => 'Verworfen',
];
$providerLabel = ['trendmicro' => 'Trend Vision One', 'defender' => 'Microsoft Defender XDR'];
?>
<style>
/* XDR-Modul – modul-spezifische Ergänzungen (Prefix xdr-).
   Standardkomponenten (page-body, kpi-row, small-box, table-wrapper,
   data-table, status-pill, subnav, empty-state, filter-card-body) aus app.css. */
.xdr-subnav { flex:1 1 100%; margin-bottom:0; }
.xdr-filterbar { display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; flex:1 1 100%; width:100%; }
.xdr-mitre { display:inline-block; background:var(--gray-100); border:1px solid var(--gray-200); border-radius:4px; font-size:.7rem; padding:.05rem .4rem; color:var(--gray-700); margin:.05rem .1rem; }
.xdr-unassigned { font-weight:600; }
.xdr-title-cell { max-width:340px; }
.xdr-title-cell small { color:var(--gray-500); display:block; }
</style>

<div class="content-header">
    <div>
        <h1><?= $this->te('XDR-Sicherheitswarnungen') ?></h1>
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
            <li class="breadcrumb-item active">XDR</li>
        </ol>
    </div>
</div>

<div class="page-body">

    <div class="kpi-row">
        <div class="kpi-col">
            <a href="/xdr">
            <div class="small-box bg-info">
                <div class="inner"><h3><?= (int)$kpis['open'] ?></h3><p><?= $this->te('Offene Warnungen') ?></p></div>
                <div class="icon"><i class="fas fa-shield-virus"></i></div>
            </div>
            </a>
        </div>
        <div class="kpi-col">
            <a href="/xdr?severity=critical">
            <div class="small-box bg-danger">
                <div class="inner"><h3><?= (int)$kpis['severe'] ?></h3><p><?= $this->te('Kritisch / Hoch') ?></p></div>
                <div class="icon"><i class="fas fa-exclamation-triangle"></i></div>
            </div>
            </a>
        </div>
        <div class="kpi-col">
            <a href="/xdr?focus=unassigned">
            <div class="small-box bg-warning">
                <div class="inner"><h3><?= (int)$kpis['unmatched'] ?></h3><p><?= $this->te('Nicht zugeordnet') ?></p></div>
                <div class="icon"><i class="fas fa-question-circle"></i></div>
            </div>
            </a>
        </div>
        <div class="kpi-col">
            <div class="small-box bg-secondary">
                <div class="inner" style="font-size:.95rem;">
                    <h3 style="font-size:1.1rem;"><?= $kpis['last_sync'] ? $view->e(date('d.m.Y H:i', strtotime($kpis['last_sync'] . ' UTC'))) : '–' ?></h3>
                    <p><?= $this->te('Letzter Abgleich') ?></p>
                </div>
                <div class="icon"><i class="fas fa-sync"></i></div>
            </div>
        </div>
    </div>

    <!-- Filter-Card: Ansichten-Tabs + Filter (wie /orders, /status) -->
    <div class="card card-outline card-primary mb-3">
        <div class="filter-card-body">
            <div class="subnav xdr-subnav">
                <a href="/xdr" class="subnav-tab <?= $filter['match'] === '' ? 'active' : '' ?>"><?= $this->te('Alle Warnungen') ?></a>
                <a href="/xdr?focus=unassigned" class="subnav-tab <?= $filter['match'] === 'unmatched' ? 'active' : '' ?>"><?= $this->te('Nicht zugeordnet') ?></a>
            </div>
            <form method="get" action="/xdr" class="xdr-filterbar">
                <div class="filter-input-wrap" style="flex:1;min-width:200px;">
                    <svg class="filter-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                    <input type="search" name="q" class="form-control form-control-sm" value="<?= $view->e($filter['q']) ?>" placeholder="<?= $this->te('Gerät oder Titel…') ?>">
                </div>
                <select name="provider" class="form-control form-control-sm" style="width:auto;">
                    <option value=""><?= $this->te('Alle Anbieter') ?></option>
                    <?php foreach ($providers as $p): ?>
                        <option value="<?= $view->e($p) ?>" <?= $filter['provider'] === $p ? 'selected' : '' ?>>
                            <?= $view->e($providerLabel[$p] ?? $p) ?>
                        </option>
                    <?php endforeach; ?>
                </select>
                <select name="severity" class="form-control form-control-sm" style="width:auto;">
                    <option value=""><?= $this->te('Alle Schweregrade') ?></option>
                    <?php foreach ($sevLabel as $k => $lbl): ?>
                        <option value="<?= $k ?>" <?= $filter['severity'] === $k ? 'selected' : '' ?>><?= $lbl ?></option>
                    <?php endforeach; ?>
                </select>
                <select name="status" class="form-control form-control-sm" style="width:auto;">
                    <option value=""><?= $this->te('Alle Status') ?></option>
                    <?php foreach ($statusLabel as $k => $lbl): ?>
                        <option value="<?= $k ?>" <?= $filter['status'] === $k ? 'selected' : '' ?>><?= $lbl ?></option>
                    <?php endforeach; ?>
                </select>
                <select name="match" class="form-control form-control-sm" style="width:auto;">
                    <option value=""><?= $this->te('Alle Zuordnungen') ?></option>
                    <option value="matched"   <?= $filter['match'] === 'matched' ? 'selected' : '' ?>><?= $this->te('Zugeordnet') ?></option>
                    <option value="unmatched" <?= $filter['match'] === 'unmatched' ? 'selected' : '' ?>><?= $this->te('Nicht zugeordnet') ?></option>
                    <option value="ambiguous" <?= $filter['match'] === 'ambiguous' ? 'selected' : '' ?>><?= $this->te('Mehrdeutig') ?></option>
                </select>
                <button type="submit" class="btn btn-primary btn-sm"><?= $this->te('Filtern') ?></button>
                <a href="/xdr" class="btn btn-default btn-sm"><?= $this->te('Zurücksetzen') ?></a>
            </form>
        </div>
    </div>

    <div class="table-wrapper">
        <?php if (empty($alerts)): ?>
            <div class="empty-state">
                <p><?= $this->t('Keine Warnungen gefunden. Konfiguriere unter <a href="/admin/xdr/settings">XDR-Einstellungen</a> einen Anbieter, um Warnungen zu importieren.') ?></p>
            </div>
        <?php else: ?>
        <table class="data-table">
            <thead>
                <tr>
                    <th scope="col"><?= $this->te('Schweregrad') ?></th>
                    <th scope="col"><?= $this->te('Warnung') ?></th>
                    <th scope="col"><?= $this->te('Gerät / Asset') ?></th>
                    <th scope="col"><?= $this->te('Anbieter') ?></th>
                    <th scope="col"><?= $this->te('Status') ?></th>
                    <th scope="col"><?= $this->te('Erkannt') ?></th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($alerts as $a): ?>
                    <?php
                        $sev = (string)$a['severity'];
                        $st  = (string)$a['local_status'];
                    ?>
                    <tr>
                        <td>
                            <span class="status-pill <?= $sevPill[$sev] ?? 'status-pill--neutral' ?>">
                                <?= $sevLabel[$sev] ?? $view->e($sev) ?>
                            </span>
                        </td>
                        <td class="xdr-title-cell">
                            <a href="/xdr/<?= (int)$a['id'] ?>" class="table-link"><?= $view->e($a['title']) ?></a>
                            <?php if (($a['alert_type'] ?? '') === 'oat'): ?>
                                <span class="badge">OAT</span>
                            <?php endif; ?>
                        </td>
                        <td>
                            <?php if (!empty($a['asset_id'])): ?>
                                <a href="/inventory/<?= (int)$a['asset_id'] ?>" class="table-link">
                                    <?= $view->e($a['asset_name'] ?? ('#' . $a['asset_id'])) ?>
                                </a>
                            <?php elseif (($a['match_status'] ?? '') === 'ambiguous'): ?>
                                <span class="status-pill status-pill--orange"><?= $this->te('Mehrdeutig') ?></span>
                                <small><?= $view->e($a['endpoint_name'] ?? '') ?></small>
                            <?php else: ?>
                                <span class="status-pill status-pill--danger xdr-unassigned"><?= $this->te('Nicht zugeordnet') ?></span>
                                <small><?= $view->e($a['endpoint_name'] ?? '–') ?></small>
                            <?php endif; ?>
                        </td>
                        <td><?= $view->e($providerLabel[$a['provider']] ?? $a['provider']) ?></td>
                        <td>
                            <span class="status-pill <?= $statusPill[$st] ?? 'status-pill--neutral' ?>">
                                <?= $statusLabel[$st] ?? $view->e($st) ?>
                            </span>
                            <?php if (!empty($a['assigned_to_name'])): ?>
                                <br><small><i class="fas fa-user-check"></i> <?= $view->e((string)$a['assigned_to_name']) ?></small>
                            <?php endif; ?>
                        </td>
                        <td><?= $a['detected_at'] ? $view->e(date('d.m.Y H:i', strtotime((string)$a['detected_at']))) : '–' ?></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
        <?php endif; ?>
    </div>

    <?php if ($pages > 1): ?>
    <div style="display:flex; justify-content:space-between; align-items:center; margin-top:.75rem;">
        <small><?= $this->te('{anzahl} Warnung(en)', ['anzahl' => (int)$total]) ?></small>
        <div>
            <?php
                $qs = $filter;
                $mk = function (int $p) use ($qs): string {
                    $qs['page'] = $p;
                    return '/xdr?' . http_build_query(array_filter($qs, fn($v) => $v !== '' && $v !== null));
                };
            ?>
            <?php if ($page > 1): ?><a class="btn btn-default btn-sm" href="<?= $mk($page - 1) ?>"><?= $this->te('&laquo; Zurück') ?></a><?php endif; ?>
            <span style="margin:0 .5rem;"><?= $this->te('Seite {seite} / {gesamt}', ['seite' => (int)$page, 'gesamt' => (int)$pages]) ?></span>
            <?php if ($page < $pages): ?><a class="btn btn-default btn-sm" href="<?= $mk($page + 1) ?>"><?= $this->te('Weiter &raquo;') ?></a><?php endif; ?>
        </div>
    </div>
    <?php endif; ?>

</div>