fw_rules_builder/app/templates/index.html

549 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firewall Rules Builder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-dark bg-dark px-3 py-2">
<span class="navbar-brand fw-bold"><i class="bi bi-shield-lock-fill me-2 text-info"></i>Firewall Rules Builder</span>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-warning" onclick="App.importSettings()">
<i class="bi bi-upload me-1"></i>Импорт fw_settings.py
</button>
<a class="btn btn-sm btn-outline-success" href="/api/export" download="fw_settings.py">
<i class="bi bi-download me-1"></i>Экспорт fw_settings.py
</a>
</div>
</nav>
<!-- Tabs -->
<div class="container-fluid mt-3">
<ul class="nav nav-tabs" id="mainTabs">
<li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#tab-objects"><i class="bi bi-hdd-network me-1"></i>Объекты</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-groups"><i class="bi bi-collection me-1"></i>Группы объектов</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-services"><i class="bi bi-gear me-1"></i>Сервисы</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-svcgroups"><i class="bi bi-layers me-1"></i>Группы сервисов</a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#tab-rules"><i class="bi bi-list-check me-1"></i>Правила</a></li>
</ul>
<div class="tab-content mt-3">
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- TAB: Объекты -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="tab-pane fade show active" id="tab-objects">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Объекты (хосты и сети)</h5>
<button class="btn btn-primary btn-sm" onclick="Objects.openModal()">
<i class="bi bi-plus-lg me-1"></i>Добавить объект
</button>
</div>
<div class="mb-2">
<input type="text" class="form-control form-control-sm w-auto d-inline-block" id="obj-search" placeholder="Поиск..." oninput="Objects.filter(this.value)">
<select class="form-select form-select-sm w-auto d-inline-block ms-2" id="obj-type-filter" onchange="Objects.filter()">
<option value="">Все типы</option>
<option value="host">host</option>
<option value="network">network</option>
</select>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle" id="objects-table">
<thead class="table-dark">
<tr>
<th>Ключ</th><th>Тип</th><th>IP</th><th>Префикс</th><th>Шлюз</th><th>Домен</th><th>Описание</th><th>Affinity</th><th style="width:90px"></th>
</tr>
</thead>
<tbody id="objects-tbody"></tbody>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- TAB: Группы объектов -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="tab-groups">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Группы объектов</h5>
<button class="btn btn-primary btn-sm" onclick="Groups.openModal()">
<i class="bi bi-plus-lg me-1"></i>Добавить группу
</button>
</div>
<div class="mb-2">
<input type="text" class="form-control form-control-sm w-auto d-inline-block" id="grp-search" placeholder="Поиск..." oninput="Groups.filter(this.value)">
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle" id="groups-table">
<thead class="table-dark">
<tr><th>Ключ</th><th>Имя</th><th>Элементы</th><th style="width:90px"></th></tr>
</thead>
<tbody id="groups-tbody"></tbody>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- TAB: Сервисы -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="tab-services">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Сервисы</h5>
<button class="btn btn-primary btn-sm" onclick="Services.openModal()">
<i class="bi bi-plus-lg me-1"></i>Добавить сервис
</button>
</div>
<div class="mb-2">
<input type="text" class="form-control form-control-sm w-auto d-inline-block" id="svc-search" placeholder="Поиск..." oninput="Services.filter(this.value)">
<select class="form-select form-select-sm w-auto d-inline-block ms-2" id="svc-proto-filter" onchange="Services.filter()">
<option value="">Все протоколы</option>
<option value="tcp">tcp</option>
<option value="udp">udp</option>
<option value="icmp-request">icmp</option>
</select>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle">
<thead class="table-dark">
<tr><th>Ключ</th><th>Имя</th><th>Протокол</th><th>Порт источника</th><th>Порт назначения</th><th style="width:90px"></th></tr>
</thead>
<tbody id="services-tbody"></tbody>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- TAB: Группы сервисов -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="tab-svcgroups">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Группы сервисов</h5>
<button class="btn btn-primary btn-sm" onclick="SvcGroups.openModal()">
<i class="bi bi-plus-lg me-1"></i>Добавить группу сервисов
</button>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle">
<thead class="table-dark">
<tr><th>Ключ</th><th>Имя</th><th>Сервисы</th><th style="width:90px"></th></tr>
</thead>
<tbody id="svcgroups-tbody"></tbody>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════ -->
<!-- TAB: Правила -->
<!-- ═══════════════════════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="tab-rules">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0">Правила Firewall</h5>
<div class="d-flex gap-2">
<button class="btn btn-secondary btn-sm" onclick="Rules.openSpanModal()">
<i class="bi bi-dash-lg me-1"></i>Добавить разделитель
</button>
<button class="btn btn-primary btn-sm" onclick="Rules.openModal()">
<i class="bi bi-plus-lg me-1"></i>Добавить правило
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle" id="rules-table">
<thead class="table-dark">
<tr>
<th style="width:40px">#</th>
<th>Порядок</th>
<th>Имя</th>
<th>Описание</th>
<th>Источник</th>
<th>Назначение</th>
<th>Сервисы</th>
<th>Действие</th>
<th>Лог</th>
<th>IDP</th>
<th>Affinity</th>
<th style="width:100px"></th>
</tr>
</thead>
<tbody id="rules-tbody"></tbody>
</table>
</div>
</div>
</div><!-- /tab-content -->
</div><!-- /container -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<!-- MODAL: Объект -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<div class="modal fade" id="modal-object" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title" id="modal-object-title">Объект</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="form-object">
<input type="hidden" id="obj-edit-key">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label">Ключ <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="obj-key" required placeholder="cr">
</div>
<div class="col-md-4">
<label class="form-label">Hostname</label>
<input type="text" class="form-control" id="obj-hostname" placeholder="cr">
</div>
<div class="col-md-4">
<label class="form-label">Тип <span class="text-danger">*</span></label>
<select class="form-select" id="obj-type">
<option value="host">host</option>
<option value="network">network</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">IP-адрес</label>
<input type="text" class="form-control" id="obj-ip" placeholder="172.19.20.2">
</div>
<div class="col-md-2">
<label class="form-label">Префикс</label>
<input type="text" class="form-control" id="obj-prefix" placeholder="24">
</div>
<div class="col-md-4">
<label class="form-label">Шлюз</label>
<input type="text" class="form-control" id="obj-gw" placeholder="172.19.20.1">
</div>
<div class="col-md-2">
<label class="form-label">Домен</label>
<input type="text" class="form-control" id="obj-domain" placeholder="avndr.ru">
</div>
<div class="col-12">
<label class="form-label">Описание</label>
<input type="text" class="form-control" id="obj-description" placeholder="Описание объекта">
</div>
<div class="col-12">
<label class="form-label">Affinity (через запятую)</label>
<input type="text" class="form-control" id="obj-affinity" placeholder="fw_cr, fw_cr_ca">
</div>
</div>
<div id="obj-error" class="alert alert-danger mt-2 d-none"></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="Objects.save()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<!-- MODAL: Группа объектов -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<div class="modal fade" id="modal-group" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title" id="modal-group-title">Группа объектов</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="form-group">
<input type="hidden" id="grp-edit-key">
<div class="row g-2 mb-2">
<div class="col-md-5">
<label class="form-label">Ключ <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="grp-key" required placeholder="set_dr">
</div>
<div class="col-md-7">
<label class="form-label">Имя</label>
<input type="text" class="form-control" id="grp-name" placeholder="set_dr">
</div>
</div>
<label class="form-label">Элементы группы</label>
<div class="row g-2 mb-2">
<div class="col-md-8">
<input type="text" class="form-control" id="grp-item-search" placeholder="Поиск объекта..." oninput="Groups.filterItems(this.value)">
</div>
<div class="col-md-4">
<select class="form-select" id="grp-item-type-filter" onchange="Groups.filterItems()">
<option value="">Все типы</option>
<option value="host">host</option>
<option value="network">network</option>
</select>
</div>
</div>
<div class="row g-2">
<div class="col-md-6">
<div class="fw-bold small mb-1 text-muted">Доступные объекты</div>
<div class="border rounded p-1" style="height:260px;overflow-y:auto;" id="grp-available-list"></div>
</div>
<div class="col-md-6">
<div class="fw-bold small mb-1 text-muted">Выбранные элементы <span class="badge bg-primary" id="grp-selected-count">0</span></div>
<div class="border rounded p-1" style="height:260px;overflow-y:auto;" id="grp-selected-list"></div>
</div>
</div>
<div id="grp-error" class="alert alert-danger mt-2 d-none"></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="Groups.save()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<!-- MODAL: Сервис -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<div class="modal fade" id="modal-service" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title" id="modal-service-title">Сервис</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="form-service">
<input type="hidden" id="svc-edit-key">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label">Ключ <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="svc-key" required placeholder="ssh">
</div>
<div class="col-md-6">
<label class="form-label">Имя</label>
<input type="text" class="form-control" id="svc-name" placeholder="ssh-22-tcp">
</div>
<div class="col-md-4">
<label class="form-label">Протокол</label>
<select class="form-select" id="svc-proto">
<option value="tcp">tcp</option>
<option value="udp">udp</option>
<option value="icmp-request">icmp-request</option>
<option value="any">any</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Порт источника</label>
<input type="text" class="form-control" id="svc-sport" placeholder="any">
</div>
<div class="col-md-4">
<label class="form-label">Порт назначения</label>
<input type="text" class="form-control" id="svc-dport" placeholder="22">
</div>
</div>
<div id="svc-error" class="alert alert-danger mt-2 d-none"></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="Services.save()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<!-- MODAL: Группа сервисов -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<div class="modal fade" id="modal-svcgroup" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title" id="modal-svcgroup-title">Группа сервисов</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="form-svcgroup">
<input type="hidden" id="sg-edit-key">
<div class="row g-2 mb-2">
<div class="col-md-5">
<label class="form-label">Ключ <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="sg-key" required placeholder="sg_dns">
</div>
<div class="col-md-7">
<label class="form-label">Имя</label>
<input type="text" class="form-control" id="sg-name" placeholder="sg_dns">
</div>
</div>
<label class="form-label">Сервисы в группе</label>
<div class="mb-2">
<input type="text" class="form-control" id="sg-svc-search" placeholder="Поиск сервиса..." oninput="SvcGroups.filterItems(this.value)">
</div>
<div class="row g-2">
<div class="col-md-6">
<div class="fw-bold small mb-1 text-muted">Доступные сервисы</div>
<div class="border rounded p-1" style="height:240px;overflow-y:auto;" id="sg-available-list"></div>
</div>
<div class="col-md-6">
<div class="fw-bold small mb-1 text-muted">Выбранные сервисы <span class="badge bg-primary" id="sg-selected-count">0</span></div>
<div class="border rounded p-1" style="height:240px;overflow-y:auto;" id="sg-selected-list"></div>
</div>
</div>
<div id="sg-error" class="alert alert-danger mt-2 d-none"></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="SvcGroups.save()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<!-- MODAL: Правило -->
<!-- ═══════════════════════════════════════════════════════════════════════════ -->
<div class="modal fade" id="modal-rule" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title" id="modal-rule-title">Правило</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="form-rule">
<input type="hidden" id="rule-edit-idx">
<div class="row g-2 mb-3">
<div class="col-md-5">
<label class="form-label">Имя правила <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="rule-name" required placeholder="to_dns">
</div>
<div class="col-md-2">
<label class="form-label">Порядок</label>
<input type="number" class="form-control" id="rule-order" placeholder="1000">
</div>
<div class="col-md-3">
<label class="form-label">Действие</label>
<select class="form-select" id="rule-action">
<option value="allow">allow</option>
<option value="deny">deny</option>
<option value="drop">drop</option>
<option value="reject">reject</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label">Лог</label>
<select class="form-select" id="rule-log">
<option value="false">false</option>
<option value="true">true</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label">IDP</label>
<select class="form-select" id="rule-idp">
<option value="false">false</option>
<option value="true">true</option>
</select>
</div>
<div class="col-12">
<label class="form-label">Описание</label>
<input type="text" class="form-control" id="rule-description" placeholder="Описание правила">
</div>
<div class="col-12">
<label class="form-label">Affinity (через запятую)</label>
<input type="text" class="form-control" id="rule-affinity" placeholder="fw_cr, fw_cr_ca">
</div>
</div>
<!-- Источник / Назначение / Сервисы -->
<div class="row g-3">
<!-- Источник -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header py-1 fw-bold">Источник (src)</div>
<div class="card-body p-2">
<input type="text" class="form-control form-control-sm mb-1" id="rule-src-search" placeholder="Поиск..." oninput="Rules.filterSrc(this.value)">
<div class="fw-bold small text-muted mb-1">Доступные</div>
<div class="border rounded p-1 mb-2" style="height:180px;overflow-y:auto;" id="rule-src-available"></div>
<div class="fw-bold small text-muted mb-1">Выбранные <span class="badge bg-primary" id="rule-src-count">0</span></div>
<div class="border rounded p-1" style="height:120px;overflow-y:auto;" id="rule-src-selected"></div>
</div>
</div>
</div>
<!-- Назначение -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header py-1 fw-bold">Назначение (dst)</div>
<div class="card-body p-2">
<input type="text" class="form-control form-control-sm mb-1" id="rule-dst-search" placeholder="Поиск..." oninput="Rules.filterDst(this.value)">
<div class="fw-bold small text-muted mb-1">Доступные</div>
<div class="border rounded p-1 mb-2" style="height:180px;overflow-y:auto;" id="rule-dst-available"></div>
<div class="fw-bold small text-muted mb-1">Выбранные <span class="badge bg-primary" id="rule-dst-count">0</span></div>
<div class="border rounded p-1" style="height:120px;overflow-y:auto;" id="rule-dst-selected"></div>
</div>
</div>
</div>
<!-- Сервисы -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header py-1 fw-bold">Сервисы</div>
<div class="card-body p-2">
<input type="text" class="form-control form-control-sm mb-1" id="rule-svc-search" placeholder="Поиск..." oninput="Rules.filterSvc(this.value)">
<div class="fw-bold small text-muted mb-1">Доступные</div>
<div class="border rounded p-1 mb-2" style="height:180px;overflow-y:auto;" id="rule-svc-available"></div>
<div class="fw-bold small text-muted mb-1">Выбранные <span class="badge bg-primary" id="rule-svc-count">0</span></div>
<div class="border rounded p-1" style="height:120px;overflow-y:auto;" id="rule-svc-selected"></div>
</div>
</div>
</div>
</div>
<div id="rule-error" class="alert alert-danger mt-2 d-none"></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="Rules.save()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- MODAL: Разделитель (span) -->
<div class="modal fade" id="modal-span" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-secondary text-white">
<h5 class="modal-title">Разделитель (span)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="span-edit-idx">
<div class="mb-2">
<label class="form-label">Название секции</label>
<input type="text" class="form-control" id="span-name" placeholder="Инфраструктурные правила">
</div>
<div class="mb-2">
<label class="form-label">Порядок</label>
<input type="number" class="form-control" id="span-order" placeholder="1000">
</div>
<div class="mb-2">
<label class="form-label">Affinity (через запятую)</label>
<input type="text" class="form-control" id="span-affinity" placeholder="fw_cr">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" onclick="Rules.saveSpan()">Сохранить</button>
</div>
</div>
</div>
</div>
<!-- Toast уведомления -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" id="toast-container"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/app.js"></script>
</body>
</html>