Stabilize discovery workflow
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<?php if (empty($findings)): ?>
|
||||
<div class="alert alert-info">Находок по выбранным фильтрам нет.</div>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$sort = $filters['sort'] ?? 'ip';
|
||||
$dir = $filters['dir'] ?? 'asc';
|
||||
$sortLink = static function (string $field, string $label) use ($filters, $sort, $dir): string {
|
||||
$nextDir = ($sort === $field && $dir === 'asc') ? 'desc' : 'asc';
|
||||
$params = array_filter([
|
||||
'range_id' => $filters['range_id'] ?? null,
|
||||
'scan_job_id' => $filters['scan_job_id'] ?? null,
|
||||
'status' => $filters['status'] ?? 'all',
|
||||
'sort' => $field,
|
||||
'dir' => $nextDir,
|
||||
], static fn ($value) => $value !== null && $value !== '');
|
||||
$mark = $sort === $field ? ($dir === 'asc' ? ' ↑' : ' ↓') : '';
|
||||
return '<a href="/discoveries?' . htmlspecialchars(http_build_query($params)) . '" class="text-dark text-decoration-none">' .
|
||||
htmlspecialchars($label . $mark) . '</a>';
|
||||
};
|
||||
?>
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $sortLink('ip', 'IP') ?></th>
|
||||
<th><?= $sortLink('hostname', 'Hostname') ?></th>
|
||||
<th><?= $sortLink('mac', 'MAC') ?></th>
|
||||
<th><?= $sortLink('vendor', 'Vendor') ?></th>
|
||||
<th><?= $sortLink('ports', 'Порты') ?></th>
|
||||
<th><?= $sortLink('range', 'Диапазон') ?></th>
|
||||
<th><?= $sortLink('status', 'Статус') ?></th>
|
||||
<th><?= $sortLink('device', 'Устройство') ?></th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($findings as $host): ?>
|
||||
<?php $ports = json_decode($host['open_ports_json'] ?? '[]', true) ?: []; ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($host['ip_address']) ?></td>
|
||||
<td><?= htmlspecialchars($host['hostname'] ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($host['mac_address'] ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($host['vendor'] ?? '-') ?></td>
|
||||
<td class="ports-cell">
|
||||
<?php if (empty($ports)): ?>
|
||||
-
|
||||
<?php else: ?>
|
||||
<?= htmlspecialchars(implode(', ', array_slice($ports, 0, 6))) ?>
|
||||
<?php if (count($ports) > 6): ?>
|
||||
<span class="text-muted">+<?= count($ports) - 6 ?></span>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($host['range_name'])): ?>
|
||||
<?= htmlspecialchars($host['range_name']) ?>
|
||||
<small class="text-muted"><?= htmlspecialchars($host['range_cidr'] ?? '') ?></small>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($host['status']) ?></td>
|
||||
<td>
|
||||
<?php if (!empty($host['device_id'])): ?>
|
||||
<a href="/devices/<?= (int)$host['device_id'] ?>" class="badge text-bg-success text-decoration-none">
|
||||
<?= htmlspecialchars($host['device_name'] ?? 'Устройство создано') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$suggestedDeviceId = null;
|
||||
$suggestedDeviceName = null;
|
||||
$suggestionReason = null;
|
||||
$suggestionConfidence = null;
|
||||
if (!empty($host['suggested_mac_device_id'])) {
|
||||
$suggestedDeviceId = (int)$host['suggested_mac_device_id'];
|
||||
$suggestedDeviceName = $host['suggested_mac_device_name'];
|
||||
$suggestionReason = 'MAC';
|
||||
$suggestionConfidence = 90;
|
||||
} elseif (!empty($host['suggested_hostname_device_id'])) {
|
||||
$suggestedDeviceId = (int)$host['suggested_hostname_device_id'];
|
||||
$suggestedDeviceName = $host['suggested_hostname_device_name'];
|
||||
$suggestionReason = 'hostname';
|
||||
$suggestionConfidence = 60;
|
||||
} elseif (!empty($host['suggested_ip_device_id'])) {
|
||||
$suggestedDeviceId = (int)$host['suggested_ip_device_id'];
|
||||
$suggestedDeviceName = $host['suggested_ip_device_name'];
|
||||
$suggestionReason = 'IP';
|
||||
$suggestionConfidence = 40;
|
||||
}
|
||||
?>
|
||||
<?php if ($suggestedDeviceId !== null): ?>
|
||||
<span class="badge text-bg-warning">
|
||||
Похоже на <?= htmlspecialchars($suggestedDeviceName ?? ('#' . $suggestedDeviceId)) ?>
|
||||
(<?= htmlspecialchars($suggestionReason ?? '') ?>, <?= (int)$suggestionConfidence ?>%)
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="badge text-bg-secondary">нет</span>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (empty($host['device_id']) && ($suggestedDeviceId ?? null) !== null && $host['status'] === 'new'): ?>
|
||||
<form method="POST" action="/discoveries/merge" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= (int)$host['id'] ?>">
|
||||
<input type="hidden" name="device_id" value="<?= (int)$suggestedDeviceId ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-success">Объединить</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (empty($host['device_id']) && $host['status'] === 'new'): ?>
|
||||
<form method="POST" action="/devices/from-host" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= (int)$host['id'] ?>">
|
||||
<input type="hidden" name="name" value="<?= htmlspecialchars($host['hostname'] ?: $host['ip_address']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Создать устройство</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if ($host['status'] === 'new'): ?>
|
||||
<form method="POST" action="/discovery/hosts/ignore" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= (int)$host['id'] ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Игнорировать</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php ob_start(); ?>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="mb-0">Находки</h2>
|
||||
</div>
|
||||
|
||||
<form method="GET"
|
||||
action="/discoveries"
|
||||
class="row g-2 mb-4"
|
||||
hx-get="/discoveries/table"
|
||||
hx-target="#discoveries-table"
|
||||
hx-trigger="change">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Диапазон</label>
|
||||
<select name="range_id" class="form-select">
|
||||
<option value="">Все диапазоны</option>
|
||||
<?php foreach ($ranges as $range): ?>
|
||||
<option value="<?= (int)$range->id ?>" <?= $filters['range_id'] === $range->id ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($range->name) ?> (<?= htmlspecialchars($range->cidr) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Статус</label>
|
||||
<select name="status" class="form-select">
|
||||
<?php foreach (['new' => 'Новые', 'accepted' => 'Принятые', 'ignored' => 'Игнорированные', 'all' => 'Все'] as $value => $label): ?>
|
||||
<option value="<?= $value ?>" <?= ($filters['status'] ?? null) === ($value === 'all' ? null : $value) ? 'selected' : '' ?>>
|
||||
<?= $label ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($filters['scan_job_id'] !== null): ?>
|
||||
<input type="hidden" name="scan_job_id" value="<?= (int)$filters['scan_job_id'] ?>">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Сканирование</label>
|
||||
<input type="text" class="form-control" value="#<?= (int)$filters['scan_job_id'] ?>" disabled>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="sort" value="<?= htmlspecialchars($filters['sort']) ?>">
|
||||
<input type="hidden" name="dir" value="<?= htmlspecialchars($filters['dir']) ?>">
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-outline-primary w-100">Применить</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="discoveries-table"
|
||||
hx-get="/discoveries/table?<?= htmlspecialchars(http_build_query(array_filter([
|
||||
'range_id' => $filters['range_id'],
|
||||
'scan_job_id' => $filters['scan_job_id'],
|
||||
'status' => $filters['status'] ?? 'all',
|
||||
'sort' => $filters['sort'],
|
||||
'dir' => $filters['dir'],
|
||||
], static fn ($value) => $value !== null && $value !== ''))) ?>"
|
||||
hx-trigger="every 2s"
|
||||
hx-swap="innerHTML">
|
||||
<?php require dirname(__DIR__) . '/discoveries/_table.php'; ?>
|
||||
</div>
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
@@ -0,0 +1,83 @@
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Активные сканирования</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($activeJobs)): ?>
|
||||
<p class="text-muted mb-0">Нет активных сканирований.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Тип</th>
|
||||
<th>Статус</th>
|
||||
<th>Прогресс</th>
|
||||
<th>Найдено</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($activeJobs as $job): ?>
|
||||
<?php $progress = json_decode($job->resultJson ?? '{}', true) ?: []; ?>
|
||||
<?php
|
||||
$totalIps = (int)($progress['total_ips'] ?? 0);
|
||||
$nextIpIndex = (int)($progress['next_ip_index'] ?? 0);
|
||||
$hostsFound = (int)($progress['hosts_found'] ?? 0);
|
||||
$percent = $totalIps > 0 ? min(100, (int)floor(($nextIpIndex / $totalIps) * 100)) : 0;
|
||||
$badgeClass = match ($job->status) {
|
||||
'pending' => 'bg-warning',
|
||||
'running' => 'bg-info',
|
||||
'paused' => 'bg-secondary',
|
||||
default => 'bg-light text-dark',
|
||||
};
|
||||
?>
|
||||
<tr>
|
||||
<td>#<?= (int)$job->id ?></td>
|
||||
<td><?= htmlspecialchars($job->type) ?></td>
|
||||
<td><span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($job->status) ?></span></td>
|
||||
<td style="min-width: 180px;">
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div class="progress-bar" style="width: <?= $percent ?>%"></div>
|
||||
</div>
|
||||
<small class="text-muted"><?= $nextIpIndex ?> / <?= $totalIps ?: '?' ?> IP</small>
|
||||
</td>
|
||||
<td><?= $hostsFound ?></td>
|
||||
<td>
|
||||
<a href="/discoveries?scan_job_id=<?= (int)$job->id ?>&status=all"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
Находки
|
||||
</a>
|
||||
<?php if ($job->status === 'running'): ?>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
hx-post="/discovery/jobs/<?= (int)$job->id ?>/pause"
|
||||
hx-target="#active-jobs"
|
||||
hx-swap="innerHTML">
|
||||
Пауза
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($job->status === 'paused'): ?>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
hx-post="/discovery/jobs/<?= (int)$job->id ?>/resume"
|
||||
hx-target="#active-jobs"
|
||||
hx-swap="innerHTML">
|
||||
Продолжить
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($job->status, ['pending', 'running', 'paused'], true)): ?>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
hx-post="/discovery/jobs/<?= (int)$job->id ?>/cancel"
|
||||
hx-target="#active-jobs"
|
||||
hx-swap="innerHTML">
|
||||
Отменить
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,59 @@
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">История сканирования</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($scanJobs)): ?>
|
||||
<p class="text-muted">Нет запусков сканирования.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Тип</th>
|
||||
<th>Статус</th>
|
||||
<th>Результат</th>
|
||||
<th>Запущен</th>
|
||||
<th>Завершён</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($scanJobs as $job): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($job->type) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$badgeClass = match ($job->status) {
|
||||
'pending' => 'bg-warning',
|
||||
'running' => 'bg-info',
|
||||
'done' => 'bg-success',
|
||||
'failed' => 'bg-danger',
|
||||
'paused' => 'bg-secondary',
|
||||
'cancelled' => 'bg-dark',
|
||||
default => 'bg-secondary',
|
||||
};
|
||||
?>
|
||||
<span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($job->status) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($job->errorMessage !== null): ?>
|
||||
<span class="text-danger"><?= htmlspecialchars($job->errorMessage) ?></span>
|
||||
<?php elseif ($job->resultJson !== null): ?>
|
||||
<?php $result = json_decode($job->resultJson, true) ?: []; ?>
|
||||
<?= isset($result['hosts_found']) ? (int)$result['hosts_found'] . ' хост(ов)' : '-' ?>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= $job->startedAt?->format('Y-m-d H:i:s') ?? '-' ?></td>
|
||||
<td><?= $job->finishedAt?->format('Y-m-d H:i:s') ?? '-' ?></td>
|
||||
<td>
|
||||
<a href="/discoveries?scan_job_id=<?= (int)$job->id ?>&status=all" class="btn btn-sm btn-outline-primary">
|
||||
Находки
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,6 +48,9 @@
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/discoveries?range_id=<?= (int)$range->id ?>&status=all" class="btn btn-sm btn-outline-primary">
|
||||
Находки
|
||||
</a>
|
||||
<form method="POST" action="/discovery/ranges/toggle" class="d-inline">
|
||||
<input type="hidden" name="id" value="<?= $range->id ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">
|
||||
@@ -73,93 +76,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scan Jobs -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">История сканирования</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($scanJobs)): ?>
|
||||
<p class="text-muted">Нет запусков сканирования.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Тип</th>
|
||||
<th>Статус</th>
|
||||
<th>Запущен</th>
|
||||
<th>Завершён</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($scanJobs as $job): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($job->type) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$badgeClass = match ($job->status) {
|
||||
'pending' => 'bg-warning',
|
||||
'running' => 'bg-info',
|
||||
'done' => 'bg-success',
|
||||
'failed' => 'bg-danger',
|
||||
default => 'bg-secondary',
|
||||
};
|
||||
?>
|
||||
<span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($job->status) ?></span>
|
||||
</td>
|
||||
<td><?= $job->startedAt?->format('Y-m-d H:i:s') ?? '-' ?></td>
|
||||
<td><?= $job->finishedAt?->format('Y-m-d H:i:s') ?? '-' ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="active-jobs"
|
||||
hx-get="/discovery/jobs/active"
|
||||
hx-trigger="load, every 2s"
|
||||
hx-swap="innerHTML">
|
||||
<?php require dirname(__DIR__) . '/discovery/_active_jobs.php'; ?>
|
||||
</div>
|
||||
|
||||
<!-- New Hosts -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Новые находки (<?= count($newHosts) ?>)</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($newHosts)): ?>
|
||||
<p class="text-muted">Нет новых находок.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th>Hostname</th>
|
||||
<th>MAC</th>
|
||||
<th>Vendor</th>
|
||||
<th>Порты</th>
|
||||
<th>Уверенность</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($newHosts as $host): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($host->ipAddress) ?></td>
|
||||
<td><?= htmlspecialchars($host->hostname ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($host->macAddress ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($host->vendor ?? '-') ?></td>
|
||||
<td><?= empty($host->openPorts) ? '-' : implode(', ', $host->openPorts) ?></td>
|
||||
<td><?= $host->confidence ?>%</td>
|
||||
<td>
|
||||
<form method="POST" action="/devices/from-host" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= $host->id ?>">
|
||||
<input type="hidden" name="name" value="<?= htmlspecialchars($host->hostname ?: $host->ipAddress) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Создать устройство</button>
|
||||
</form>
|
||||
<form method="POST" action="/discovery/hosts/ignore" class="d-inline">
|
||||
<input type="hidden" name="host_id" value="<?= $host->id ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Игнорировать</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="scan-history"
|
||||
hx-get="/discovery/jobs/history"
|
||||
hx-trigger="load, every 2s"
|
||||
hx-swap="innerHTML">
|
||||
<?php require dirname(__DIR__) . '/discovery/_scan_history.php'; ?>
|
||||
</div>
|
||||
|
||||
<?php $content = ob_get_clean(); ?>
|
||||
<?php require dirname(__DIR__) . '/layout.php'; ?>
|
||||
|
||||
+14
-11
@@ -11,7 +11,7 @@
|
||||
<div class="d-flex" id="wrapper">
|
||||
<!-- Sidebar -->
|
||||
<nav class="bg-dark text-white border-end" id="sidebar-wrapper" style="width: 250px; min-height: 100vh;">
|
||||
<div class="sidebar-heading p-3 border-bottom border-secondary">
|
||||
<div class="sidebar-heading px-3 border-bottom border-secondary">
|
||||
<a href="/dashboard" class="text-white text-decoration-none">
|
||||
<h4 class="mb-0">Домовой</h4>
|
||||
</a>
|
||||
@@ -23,18 +23,21 @@
|
||||
<a href="/discovery" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Сканирование сети
|
||||
</a>
|
||||
<a href="/discoveries" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Находки
|
||||
</a>
|
||||
<a href="/devices" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Устройства
|
||||
</a>
|
||||
<a href="/services" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Сервисы
|
||||
</a>
|
||||
<a href="/documents" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Документы
|
||||
</a>
|
||||
<a href="/settings" class="list-group-item list-group-item-action bg-dark text-white border-0">
|
||||
Настройки
|
||||
</a>
|
||||
<span class="list-group-item bg-dark text-white-50 border-0">
|
||||
Сервисы <small class="text-secondary">позже</small>
|
||||
</span>
|
||||
<span class="list-group-item bg-dark text-white-50 border-0">
|
||||
Документы <small class="text-secondary">позже</small>
|
||||
</span>
|
||||
<span class="list-group-item bg-dark text-white-50 border-0">
|
||||
Настройки <small class="text-secondary">позже</small>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -43,7 +46,6 @@
|
||||
<!-- Top Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark border-bottom border-secondary">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand text-light">Домовой</span>
|
||||
<div class="ms-auto d-flex align-items-center">
|
||||
<span class="text-light me-3"><?= htmlspecialchars($username ?? 'User') ?></span>
|
||||
<form method="POST" action="/logout" class="d-inline">
|
||||
@@ -63,5 +65,6 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/domovoy.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user