Правки и улучшения визуала. Добавил функционал отметок.

This commit is contained in:
2025-11-16 23:32:29 +03:00
parent d9cb243388
commit 8994a0e500
30 changed files with 2495 additions and 510 deletions

View File

@@ -13,11 +13,14 @@
<div class="collapse navbar-collapse" id="navbarNav">
{% if user.is_authenticated %}
<ul class="navbar-nav me-auto">
<!-- <li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:home' %}">Главная</a>
</li> -->
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:objitem_list' %}">Объекты</a>
<a class="nav-link" href="{% url 'mainapp:source_list' %}">Объекты</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:home' %}">Источники</a>
<a class="nav-link" href="{% url 'mainapp:objitem_list' %}">Точки</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
@@ -28,6 +31,9 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'mainapp:object_marks' %}">Отметки</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
</li>

View File

@@ -0,0 +1,126 @@
<!-- ObjItems Table Component -->
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Объекты (ObjItems)</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm table-bordered mb-0" style="font-size: 0.9rem;">
<thead class="table-dark sticky-top">
<tr>
<th>ID</th>
<th>Имя</th>
<th>Спутник</th>
<th>Частота, МГц</th>
<th>Полоса, МГц</th>
<th>Поляризация</th>
<th>Модуляция</th>
<th>Сим. v</th>
<th>ОСШ</th>
<th>Геолокация</th>
<th>Дата гео</th>
<th>Объект</th>
<th>LyngSat</th>
{% if show_marks == '1' %}
<th>Отметки</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for item in processed_objitems %}
<tr>
<td><a href="{% url 'mainapp:objitem_detail' item.id %}">{{ item.id }}</a></td>
<td>{{ item.name }}</td>
<td>{{ item.satellite }}</td>
<td>{{ item.frequency }}</td>
<td>{{ item.freq_range }}</td>
<td>{{ item.polarization }}</td>
<td>{{ item.modulation }}</td>
<td>{{ item.bod_velocity }}</td>
<td>{{ item.snr }}</td>
<td>{{ item.geo_coords }}</td>
<td>{{ item.geo_date }}</td>
<td>
{% if item.source_id %}
<a href="{% url 'mainapp:source_update' item.source_id %}">{{ item.source_id }}</a>
{% else %}
-
{% endif %}
</td>
<td>
{% if item.lyngsat_id %}
<a href="{% url 'admin:lyngsatapp_lyngsat_change' item.lyngsat_id %}" target="_blank">
<i class="bi bi-link-45deg"></i>
</a>
{% else %}
-
{% endif %}
</td>
{% if show_marks == '1' %}
<td>
{% if item.marks %}
<div style="max-height: 150px; overflow-y: auto;">
{% for mark in item.marks %}
<div class="mb-1">
<span class="mark-badge {% if mark.mark %}mark-present{% else %}mark-absent{% endif %}">
{% if mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
</span>
<br>
<small class="text-muted">{{ mark.timestamp|date:"d.m.Y H:i" }}</small>
<br>
<small class="text-muted">{{ mark.created_by }}</small>
</div>
{% endfor %}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="{% if show_marks == '1' %}14{% else %}13{% endif %}" class="text-center py-4 text-muted">
Нет данных для выбранных фильтров
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if page_obj.paginator.num_pages > 1 %}
<div class="card-footer">
<nav>
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Предыдущая</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} из {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
<div class="text-center mt-2">
<small class="text-muted">Показано {{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }} записей</small>
</div>
</div>
{% endif %}
</div>

View File

@@ -24,7 +24,7 @@
<!-- Table container -->
<div class="flex-grow-1 overflow-auto">
<div class="table-responsive" style="height: 100%;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">

View File

@@ -180,10 +180,10 @@ function showSigmaParameterModal(parameterId) {
if (sigma.marks.length > 0) {
html += `
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-striped mb-0">
<table class="table table-sm table-striped table-bordered mb-0">
<thead class="table-light sticky-top">
<tr>
<th style="width: 20%;">Отметка</th>
<th style="width: 20%;">Наличие сигнала</th>
<th>Дата</th>
</tr>
</thead>

View File

@@ -0,0 +1,122 @@
<!-- Sources Table Component -->
<style>
.mark-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
margin: 2px 0;
}
.mark-present {
background: #28a745;
color: white;
}
.mark-absent {
background: #dc3545;
color: white;
}
</style>
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Объекты (Sources)</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm table-bordered mb-0">
<thead class="table-dark sticky-top">
<tr>
<th>ID</th>
<th>Спутники</th>
<th>Кол-во объектов</th>
<th>Усреднённые координаты</th>
<th>Координаты Кубсата</th>
<th>Координаты оперативников</th>
<th>Справочные координаты</th>
<th>Дата создания</th>
{% if show_marks == '1' %}
<th>Отметки</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for source in processed_sources %}
<tr>
<td><a href="{% url 'mainapp:source_update' source.id %}">{{ source.id }}</a></td>
<td>{{ source.satellites }}</td>
<td>{{ source.objitem_count }}</td>
<td>{{ source.coords_average }}</td>
<td>{{ source.coords_kupsat }}</td>
<td>{{ source.coords_valid }}</td>
<td>{{ source.coords_reference }}</td>
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
{% if show_marks == '1' %}
<td>
{% if source.marks %}
<div style="max-height: 150px; overflow-y: auto;">
{% for mark in source.marks %}
<div class="mb-1">
<span class="mark-badge {% if mark.mark %}mark-present{% else %}mark-absent{% endif %}">
{% if mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
</span>
<br>
<small class="text-muted">{{ mark.timestamp|date:"d.m.Y H:i" }}</small>
<br>
<small class="text-muted">{{ mark.created_by }}</small>
</div>
{% endfor %}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="{% if show_marks == '1' %}9{% else %}8{% endif %}" class="text-center py-4 text-muted">
Нет данных для выбранных фильтров
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if page_obj.paginator.num_pages > 1 %}
<div class="card-footer">
<nav>
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Предыдущая</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} из {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
<div class="text-center mt-2">
<small class="text-muted">Показано {{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }} записей</small>
</div>
</div>
{% endif %}
</div>

View File

@@ -1,422 +1,483 @@
{% extends 'mainapp/base.html' %}
{% load static %}
{% block title %}Список объектов{% endblock %}
{% block title %}Главная страница{% endblock %}
{% block extra_css %}
<style>
.filter-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.filter-group {
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
}
.filter-group-title {
font-weight: 600;
color: #495057;
margin-bottom: 10px;
font-size: 0.95rem;
}
.conditional-filters {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #e9ecef;
}
.table-container {
display: none;
margin-top: 20px;
}
.table-container.show {
display: block;
}
.btn-generate {
font-size: 1.1rem;
padding: 12px 40px;
font-weight: 600;
}
.filter-badge {
display: inline-block;
padding: 4px 10px;
background: #007bff;
color: white;
border-radius: 12px;
font-size: 0.85rem;
margin: 2px;
}
.active-filters {
margin-bottom: 15px;
}
.mark-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
margin: 2px 0;
}
.mark-present {
background: #28a745;
color: white;
}
.mark-absent {
background: #dc3545;
color: white;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2>Список объектов</h2>
</div>
</div>
<div class="container-fluid px-4 py-3">
<h2 class="mb-4">Главная страница - Динамический отчёт</h2>
<!-- Toolbar -->
<div class="row mb-3">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<div style="min-width: 300px; flex-grow: 1;">
<label for="toolbar-search" class="form-label mb-0">Поиск:</label>
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по имени, местоположению..." value="{{ search_query|default:'' }}">
</div>
<div class="ms-auto">
<button type="button" class="btn btn-outline-primary" onclick="performSearch()">Найти</button>
<button type="button" class="btn btn-outline-secondary" onclick="clearSearch()">Очистить</button>
<!-- Фильтры -->
<div class="filter-section">
<form method="get" id="filter-form">
<div class="row">
<!-- Основной выбор: Объекти или Объекты -->
<div class="col-12">
<div class="filter-group">
<div class="filter-group-title">1. Тип отображения</div>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="display_mode" id="mode_sources" value="sources"
{% if display_mode == 'sources' %}checked{% endif %} onchange="updateConditionalFilters()">
<label class="btn btn-outline-primary" for="mode_sources">Объекти (Source)</label>
<input type="radio" class="btn-check" name="display_mode" id="mode_objitems" value="objitems"
{% if display_mode == 'objitems' %}checked{% endif %} onchange="updateConditionalFilters()">
<label class="btn btn-outline-primary" for="mode_objitems">Объекты (ObjItem)</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3">
<!-- Filters Sidebar - Made narrower -->
<div class="col-md-2">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Фильтры</h5>
<form method="get" id="filter-form">
<!-- Satellite Selection - Multi-select -->
<div class="mb-2">
<label class="form-label">Спутник:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('satellite_id', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('satellite_id', false)">Снять</button>
<div class="row">
<!-- Общие фильтры -->
<div class="col-md-6">
<div class="filter-group">
<div class="filter-group-title">2. Общие фильтры</div>
<!-- Спутники -->
<div class="mb-3">
<label class="form-label">Спутники:</label>
<div class="d-flex gap-2 mb-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('satellite_id', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('satellite_id', false)">Снять</button>
</div>
<select name="satellite_id" class="form-select form-select-sm mb-2" multiple size="4">
{% for satellite in satellites %}
<option value="{{ satellite.id }}"
{% if satellite.id in selected_satellites %}selected{% endif %}>
{{ satellite.name }}
</option>
<select name="satellite_id" class="form-select form-select-sm" multiple size="5">
{% for sat in satellites %}
<option value="{{ sat.id }}" {% if sat.id in selected_satellites %}selected{% endif %}>
{{ sat.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Frequency Filter -->
<div class="mb-2">
<label class="form-label">Частота, МГц:</label>
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ freq_min|default:'' }}">
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm" placeholder="До" value="{{ freq_max|default:'' }}">
</div>
</div>
</div>
<!-- Условные фильтры (зависят от типа отображения) -->
<div class="col-md-6">
<!-- Фильтры для Объектов -->
<div class="filter-group" id="sources-filters" style="display: none;">
<div class="filter-group-title">3. Фильтры для Объектов</div>
<!-- Range Filter -->
<div class="mb-2">
<label class="form-label">Полоса, МГц:</label>
<input type="number" step="0.001" name="range_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ range_min|default:'' }}">
<input type="number" step="0.001" name="range_max" class="form-control form-control-sm" placeholder="До" value="{{ range_max|default:'' }}">
</div>
<!-- SNR Filter -->
<div class="mb-2">
<label class="form-label">ОСШ:</label>
<input type="number" step="0.001" name="snr_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ snr_min|default:'' }}">
<input type="number" step="0.001" name="snr_max" class="form-control form-control-sm" placeholder="До" value="{{ snr_max|default:'' }}">
</div>
<!-- Symbol Rate Filter -->
<div class="mb-2">
<label class="form-label">Сим. v, БОД:</label>
<input type="number" step="0.001" name="bod_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ bod_min|default:'' }}">
<input type="number" step="0.001" name="bod_max" class="form-control form-control-sm" placeholder="До" value="{{ bod_max|default:'' }}">
</div>
<!-- Removed old search input as it's now in the toolbar -->
<!-- Modulation Filter -->
<div class="mb-2">
<label class="form-label">Модуляция:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('modulation', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('modulation', false)">Снять</button>
<div class="mb-3">
<label class="form-label">Усреднённые координаты:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_coords_average" id="avg_all" value="" {% if not has_coords_average %}checked{% endif %}>
<label class="form-check-label" for="avg_all">Все</label>
</div>
<select name="modulation" class="form-select form-select-sm mb-2" multiple size="4">
{% for mod in modulations %}
<option value="{{ mod.id }}"
{% if mod.id in selected_modulations %}selected{% endif %}>
{{ mod.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Polarization Filter -->
<div class="mb-2">
<label class="form-label">Поляризация:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('polarization', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('polarization', false)">Снять</button>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_coords_average" id="avg_yes" value="1" {% if has_coords_average == '1' %}checked{% endif %}>
<label class="form-check-label" for="avg_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_coords_average" id="avg_no" value="0" {% if has_coords_average == '0' %}checked{% endif %}>
<label class="form-check-label" for="avg_no">Нет</label>
</div>
<select name="polarization" class="form-select form-select-sm mb-2" multiple size="4">
{% for pol in polarizations %}
<option value="{{ pol.id }}"
{% if pol.id in selected_polarizations %}selected{% endif %}>
{{ pol.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Kubsat Coordinates Filter -->
<div class="mb-2">
<div class="mb-3">
<label class="form-label">Координаты Кубсата:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_kupsat" id="has_kupsat_1" value="1"
{% if has_kupsat == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_1">Есть</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_kupsat" id="has_kupsat_0" value="0"
{% if has_kupsat == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_0">Нет</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="kup_all" value="" {% if not has_kupsat %}checked{% endif %}>
<label class="form-check-label" for="kup_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="kup_yes" value="1" {% if has_kupsat == '1' %}checked{% endif %}>
<label class="form-check-label" for="kup_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="kup_no" value="0" {% if has_kupsat == '0' %}checked{% endif %}>
<label class="form-check-label" for="kup_no">Нет</label>
</div>
</div>
<!-- Valid Coordinates Filter -->
<div class="mb-2">
<label class="form-label">Координаты опер. отдела:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_valid" id="has_valid_1" value="1"
{% if has_valid == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_1">Есть</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_valid" id="has_valid_0" value="0"
{% if has_valid == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_0">Нет</label>
</div>
<div class="mb-3">
<label class="form-label">Координаты оперативников:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="val_all" value="" {% if not has_valid %}checked{% endif %}>
<label class="form-check-label" for="val_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="val_yes" value="1" {% if has_valid == '1' %}checked{% endif %}>
<label class="form-check-label" for="val_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="val_no" value="0" {% if has_valid == '0' %}checked{% endif %}>
<label class="form-check-label" for="val_no">Нет</label>
</div>
</div>
<!-- Items Per Page -->
<div class="mb-2">
<label for="items-per-page" class="form-label">Элементов:</label>
<select name="items_per_page" id="items-per-page" class="form-select form-select-sm" onchange="document.getElementById('filter-form').submit();">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
{{ option }}
</option>
<div class="mb-3">
<label class="form-label">Справочные координаты:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_reference" id="ref_all" value="" {% if not has_reference %}checked{% endif %}>
<label class="form-check-label" for="ref_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_reference" id="ref_yes" value="1" {% if has_reference == '1' %}checked{% endif %}>
<label class="form-check-label" for="ref_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_reference" id="ref_no" value="0" {% if has_reference == '0' %}checked{% endif %}>
<label class="form-check-label" for="ref_no">Нет</label>
</div>
</div>
<!-- Наличие сигнала для объектов -->
<div class="conditional-filters">
<div class="mb-3">
<label class="form-label fw-bold">Отображать наличие сигнала:</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="show_marks" id="marks_src" value="1"
{% if show_marks == '1' %}checked{% endif %} onchange="toggleMarksFilters()">
<label class="form-check-label" for="marks_src">Да</label>
</div>
</div>
<div id="marks-additional-filters-src" style="{% if show_marks != '1' %}display:none;{% endif %}">
<div class="mb-3">
<label class="form-label">Период отметок:</label>
<input type="datetime-local" name="marks_date_from" class="form-control form-control-sm mb-1" value="{{ marks_date_from }}">
<input type="datetime-local" name="marks_date_to" class="form-control form-control-sm" value="{{ marks_date_to }}">
</div>
<div class="mb-3">
<label class="form-label">Статус отметок:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_all_src" value="" {% if not marks_status %}checked{% endif %}>
<label class="form-check-label" for="marks_all_src">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_present_src" value="present" {% if marks_status == 'present' %}checked{% endif %}>
<label class="form-check-label" for="marks_present_src">✓ Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_absent_src" value="absent" {% if marks_status == 'absent' %}checked{% endif %}>
<label class="form-check-label" for="marks_absent_src">✗ Нет</label>
</div>
</div>
</div>
</div>
</div>
<!-- Фильтры для Объектов -->
<div class="filter-group" id="objitems-filters" style="display: none;">
<div class="filter-group-title">3. Фильтры для Объектов</div>
<!-- Дата геолокации -->
<div class="mb-3">
<label class="form-label">Дата геолокации:</label>
<input type="date" name="date_from" class="form-control form-control-sm mb-1" value="{{ date_from }}">
<input type="date" name="date_to" class="form-control form-control-sm" value="{{ date_to }}">
</div>
<!-- Частота -->
<div class="mb-3">
<label class="form-label">Частота, МГц:</label>
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ freq_min }}">
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm" placeholder="До" value="{{ freq_max }}">
</div>
<!-- Полоса -->
<div class="mb-3">
<label class="form-label">Полоса, МГц:</label>
<input type="number" step="0.001" name="range_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ range_min }}">
<input type="number" step="0.001" name="range_max" class="form-control form-control-sm" placeholder="До" value="{{ range_max }}">
</div>
<!-- Модуляция -->
<div class="mb-3">
<label class="form-label">Модуляция:</label>
<div class="d-flex gap-2 mb-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('modulation', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('modulation', false)">Снять</button>
</div>
<select name="modulation" class="form-select form-select-sm" multiple size="4">
{% for mod in modulations %}
<option value="{{ mod.id }}" {% if mod.id in selected_modulations %}selected{% endif %}>
{{ mod.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Apply Filters and Reset Buttons -->
<div class="d-grid gap-2 mt-2">
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
<a href="?" class="btn btn-secondary btn-sm">Сбросить</a>
</div>
</form>
</div>
</div>
</div>
<!-- Main Table -->
<div class="col-md-10">
<div class="card h-100">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">
<input type="checkbox" id="select-all" class="form-check-input">
</th>
<th scope="col">Имя</th>
<th scope="col">Спутник</th>
<th scope="col">Част, МГц</th>
<th scope="col">Полоса, МГц</th>
<th scope="col">Поляр</th>
<th scope="col">Сим. v</th>
<th scope="col">Модул</th>
<th scope="col">ОСШ</th>
<th scope="col">Геолокация</th>
<th scope="col">Кубсат</th>
<th scope="col">Опер. отд</th>
<th scope="col">Гео-куб, км</th>
<th scope="col">Гео-опер, км</th>
<th scope="col">Куб-опер, км</th>
</tr>
</thead>
<tbody>
{% for item in processed_objects %}
<tr>
<td class="text-center">
<input type="checkbox" class="form-check-input item-checkbox" value="{{ item.id }}">
</td>
<td>{{ item.name }}</td>
<td>{{ item.satellite_name }}</td>
<td>{{ item.frequency }}</td>
<td>{{ item.freq_range }}</td>
<td>{{ item.polarization }}</td>
<td>{{ item.bod_velocity }}</td>
<td>{{ item.modulation }}</td>
<td>{{ item.snr }}</td>
<td>{{ item.geo_coords }}</td>
<td>{{ item.kupsat_coords }}</td>
<td>{{ item.valid_coords }}</td>
<td>{{ item.distance_geo_kup }}</td>
<td>{{ item.distance_geo_valid }}</td>
<td>{{ item.distance_kup_valid }}</td>
</tr>
{% empty %}
<tr>
<td colspan="15" class="text-center py-4">
{% if selected_satellite_id %}
Нет данных для выбранных фильтров
{% else %}
Пожалуйста, выберите спутник для отображения данных
{% endif %}
</td>
</tr>
<!-- Поляризация -->
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="d-flex gap-2 mb-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('polarization', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAll('polarization', false)">Снять</button>
</div>
<select name="polarization" class="form-select form-select-sm" multiple size="4">
{% for pol in polarizations %}
<option value="{{ pol.id }}" {% if pol.id in selected_polarizations %}selected{% endif %}>
{{ pol.name }}
</option>
{% endfor %}
</tbody>
</table>
</select>
</div>
<div class="mb-3">
<label class="form-label">Координаты геолокации:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="geo_all" value="" {% if not has_geo %}checked{% endif %}>
<label class="form-check-label" for="geo_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="geo_yes" value="1" {% if has_geo == '1' %}checked{% endif %}>
<label class="form-check-label" for="geo_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="geo_no" value="0" {% if has_geo == '0' %}checked{% endif %}>
<label class="form-check-label" for="geo_no">Нет</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Связь с LyngSat:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_lyngsat" id="lyng_all" value="" {% if not has_lyngsat %}checked{% endif %}>
<label class="form-check-label" for="lyng_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_lyngsat" id="lyng_yes" value="1" {% if has_lyngsat == '1' %}checked{% endif %}>
<label class="form-check-label" for="lyng_yes">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_lyngsat" id="lyng_no" value="0" {% if has_lyngsat == '0' %}checked{% endif %}>
<label class="form-check-label" for="lyng_no">Нет</label>
</div>
</div>
<!-- Наличие сигнала -->
<div class="conditional-filters">
<div class="mb-3">
<label class="form-label fw-bold">Отображать наличие сигнала:</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="show_marks" id="marks_obj" value="1"
{% if show_marks == '1' %}checked{% endif %} onchange="toggleMarksFilters()">
<label class="form-check-label" for="marks_obj">Да</label>
</div>
</div>
<div id="marks-additional-filters" style="{% if show_marks != '1' %}display:none;{% endif %}">
<div class="mb-3">
<label class="form-label">Период отметок:</label>
<input type="datetime-local" name="marks_date_from" class="form-control form-control-sm mb-1" value="{{ marks_date_from }}">
<input type="datetime-local" name="marks_date_to" class="form-control form-control-sm" value="{{ marks_date_to }}">
</div>
<div class="mb-3">
<label class="form-label">Статус отметок:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_all" value="" {% if not marks_status %}checked{% endif %}>
<label class="form-check-label" for="marks_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_present" value="present" {% if marks_status == 'present' %}checked{% endif %}>
<label class="form-check-label" for="marks_present">✓ Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_absent" value="absent" {% if marks_status == 'absent' %}checked{% endif %}>
<label class="form-check-label" for="marks_absent">✗ Нет</label>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination -->
{% if page_obj.paginator.num_pages > 1 %}
<nav aria-label="Page navigation" class="px-3 pb-3">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Предыдущая</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<!-- Pagination Info -->
{% if page_obj %}
<div class="px-3 pb-3 d-flex justify-content-between align-items-center">
<div>Показано {{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }} записей</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Настройки отображения -->
<div class="row">
<div class="col-12">
<div class="filter-group">
<div class="filter-group-title">4. Настройки отображения</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">Элементов на странице:</label>
<select name="items_per_page" class="form-select form-select-sm">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Кнопки -->
<div class="row">
<div class="col-12 text-center">
<button type="submit" class="btn btn-primary btn-generate">
<i class="bi bi-table"></i> Сформировать таблицу
</button>
<a href="?" class="btn btn-secondary ms-2">Сбросить фильтры</a>
</div>
</div>
</form>
</div>
<!-- Активные фильтры -->
{% if page_obj %}
<div class="active-filters">
<strong>Активные фильтры:</strong>
{% if display_mode == 'sources' %}
<span class="filter-badge">Объекти</span>
{% else %}
<span class="filter-badge">Объекты</span>
{% endif %}
{% if selected_satellites %}
<span class="filter-badge">Спутники: {{ selected_satellites|length }}</span>
{% endif %}
{% if date_from or date_to %}
<span class="filter-badge">Дата геолокации</span>
{% endif %}
{% if show_marks == '1' %}
<span class="filter-badge">С наличие сигналами</span>
{% endif %}
</div>
{% endif %}
<!-- Таблица (показывается только после генерации) -->
<div class="table-container {% if page_obj %}show{% endif %}">
{% if display_mode == 'sources' %}
{% include 'mainapp/components/_sources_table.html' %}
{% else %}
{% include 'mainapp/components/_objitems_table.html' %}
{% endif %}
</div>
</div>
{% endblock %}
<!-- JavaScript for checkbox functionality and filters -->
{% block extra_js %}
<script>
function selectAll(name, select) {
const element = document.querySelector(`select[name="${name}"]`);
if (element) {
for (let option of element.options) {
option.selected = select;
}
}
}
function updateConditionalFilters() {
const mode = document.querySelector('input[name="display_mode"]:checked').value;
const sourcesFilters = document.getElementById('sources-filters');
const objitemsFilters = document.getElementById('objitems-filters');
if (mode === 'sources') {
sourcesFilters.style.display = 'block';
objitemsFilters.style.display = 'none';
} else {
sourcesFilters.style.display = 'none';
objitemsFilters.style.display = 'block';
}
}
function toggleMarksFilters() {
const mode = document.querySelector('input[name="display_mode"]:checked')?.value;
if (mode === 'sources') {
const checkbox = document.getElementById('marks_src');
const filters = document.getElementById('marks-additional-filters-src');
if (checkbox && filters) {
filters.style.display = checkbox.checked ? 'block' : 'none';
}
} else if (mode === 'objitems') {
const checkbox = document.getElementById('marks_obj');
const filters = document.getElementById('marks-additional-filters');
if (checkbox && filters) {
filters.style.display = checkbox.checked ? 'block' : 'none';
}
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
// Select/Deselect all checkboxes
const selectAllCheckbox = document.getElementById('select-all');
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
if (selectAllCheckbox && itemCheckboxes.length > 0) {
selectAllCheckbox.addEventListener('change', function() {
itemCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
});
// Update select all checkbox state based on individual selections
itemCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked);
selectAllCheckbox.checked = allChecked;
});
});
}
// Handle multiple selection for modulations and polarizations
const modulationSelect = document.querySelector('select[name="modulation"]');
const polarizationSelect = document.querySelector('select[name="polarization"]');
// Prevent deselecting all options when Ctrl+click is used
if (modulationSelect) {
modulationSelect.addEventListener('change', function(e) {
document.getElementById('filter-form').submit();
});
}
if (polarizationSelect) {
polarizationSelect.addEventListener('change', function(e) {
document.getElementById('filter-form').submit();
});
}
// Handle kubsat and valid coords checkboxes (mutually exclusive)
// Add a function to handle radio-like behavior for these checkboxes
function setupRadioLikeCheckboxes(name) {
const checkboxes = document.querySelectorAll(`input[name="${name}"]`);
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
// If this checkbox is checked, uncheck the other
if (this.checked) {
checkboxes.forEach(other => {
if (other !== this) {
other.checked = false;
}
});
} else {
// If both are unchecked, no action needed
}
document.getElementById('filter-form').submit();
});
});
}
setupRadioLikeCheckboxes('has_kupsat');
setupRadioLikeCheckboxes('has_valid');
// Function to select/deselect all options in a select element
window.selectAllOptions = function(selectName, selectAll) {
const selectElement = document.querySelector(`select[name="${selectName}"]`);
if (selectElement) {
for (let i = 0; i < selectElement.options.length; i++) {
selectElement.options[i].selected = selectAll;
}
document.getElementById('filter-form').submit();
}
};
// Function to update the page when satellite selection changes
function updateSatelliteSelection() {
document.getElementById('filter-form').submit();
}
// Get all current filter values and return as URL parameters
function getAllFilterParams() {
const form = document.getElementById('filter-form');
const searchValue = document.getElementById('toolbar-search').value;
// Create URLSearchParams object from the form
const params = new URLSearchParams(new FormData(form));
// Add search value from toolbar if present
if (searchValue.trim() !== '') {
params.set('search', searchValue);
} else {
// Remove search parameter if empty
params.delete('search');
}
return params.toString();
}
// Function to perform search
window.performSearch = function() {
const filterParams = getAllFilterParams();
window.location.search = filterParams;
};
// Function to clear search
window.clearSearch = function() {
// Clear only the search input in the toolbar
document.getElementById('toolbar-search').value = '';
// Submit the form to update the results
const filterParams = getAllFilterParams();
window.location.search = filterParams;
};
// Handle Enter key in toolbar search
const toolbarSearch = document.getElementById('toolbar-search');
if (toolbarSearch) {
toolbarSearch.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
performSearch();
}
});
}
// Add event listener to satellite select for immediate update
const satelliteSelect = document.querySelector('select[name="satellite_id"]');
if (satelliteSelect) {
satelliteSelect.addEventListener('change', function() {
updateSatelliteSelection();
});
}
updateConditionalFilters();
toggleMarksFilters();
});
</script>
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,450 @@
{% extends 'mainapp/base.html' %}
{% block title %}Главная страница{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2>Главная страница</h2>
</div>
</div>
<div class="row g-3">
<!-- Filters Sidebar -->
<div class="col-md-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Фильтры</h5>
<form method="get" id="filter-form">
<!-- Display Mode -->
<div class="mb-3">
<label class="form-label">Отображение:</label>
<select name="display_mode" class="form-select form-select-sm" onchange="document.getElementById('filter-form').submit();">
<option value="sources" {% if display_mode == 'sources' %}selected{% endif %}>Список источников</option>
<option value="objitems" {% if display_mode == 'objitems' %}selected{% endif %}>Список объектов</option>
</select>
</div>
<!-- Satellite Selection -->
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('satellite_id', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('satellite_id', false)">Снять</button>
</div>
<select name="satellite_id" class="form-select form-select-sm" multiple size="5">
{% for satellite in satellites %}
<option value="{{ satellite.id }}"
{% if satellite.id in selected_satellites %}selected{% endif %}>
{{ satellite.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Date Range Filter -->
<div class="mb-3">
<label class="form-label">Дата геолокации:</label>
<input type="date" name="date_from" class="form-control form-control-sm mb-1" placeholder="От" value="{{ date_from }}">
<input type="date" name="date_to" class="form-control form-control-sm" placeholder="До" value="{{ date_to }}">
</div>
<!-- Frequency Filter -->
<div class="mb-3">
<label class="form-label">Частота, МГц:</label>
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ freq_min }}">
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm" placeholder="До" value="{{ freq_max }}">
</div>
<!-- Range Filter -->
<div class="mb-3">
<label class="form-label">Полоса, МГц:</label>
<input type="number" step="0.001" name="range_min" class="form-control form-control-sm mb-1" placeholder="От" value="{{ range_min }}">
<input type="number" step="0.001" name="range_max" class="form-control form-control-sm" placeholder="До" value="{{ range_max }}">
</div>
<!-- Modulation Filter -->
<div class="mb-3">
<label class="form-label">Модуляция:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('modulation', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('modulation', false)">Снять</button>
</div>
<select name="modulation" class="form-select form-select-sm" multiple size="4">
{% for mod in modulations %}
<option value="{{ mod.id }}"
{% if mod.id in selected_modulations %}selected{% endif %}>
{{ mod.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Polarization Filter -->
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('polarization', true)">Все</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllOptions('polarization', false)">Снять</button>
</div>
<select name="polarization" class="form-select form-select-sm" multiple size="4">
{% for pol in polarizations %}
<option value="{{ pol.id }}"
{% if pol.id in selected_polarizations %}selected{% endif %}>
{{ pol.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Marks Filters -->
<div class="mb-3">
<label class="form-label">Отображать отметки:</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="show_marks" id="show_marks_0" value="0"
{% if show_marks == '0' %}checked{% endif %}>
<label class="form-check-label" for="show_marks_0">Нет</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="show_marks" id="show_marks_1" value="1"
{% if show_marks == '1' %}checked{% endif %}>
<label class="form-check-label" for="show_marks_1">Да</label>
</div>
</div>
</div>
<!-- Marks Date Range (shown only if show_marks is enabled) -->
<div class="mb-3" id="marks-date-filter" style="{% if show_marks != '1' %}display:none;{% endif %}">
<label class="form-label">Период отметок:</label>
<input type="date" name="marks_date_from" class="form-control form-control-sm mb-1" placeholder="От" value="{{ marks_date_from }}">
<input type="date" name="marks_date_to" class="form-control form-control-sm" placeholder="До" value="{{ marks_date_to }}">
</div>
<!-- Marks Status Filter -->
<div class="mb-3" id="marks-status-filter" style="{% if show_marks != '1' %}display:none;{% endif %}">
<label class="form-label">Статус отметок:</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_status_all" value=""
{% if not marks_status %}checked{% endif %}>
<label class="form-check-label" for="marks_status_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_status_present" value="present"
{% if marks_status == 'present' %}checked{% endif %}>
<label class="form-check-label" for="marks_status_present">✓ Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marks_status" id="marks_status_absent" value="absent"
{% if marks_status == 'absent' %}checked{% endif %}>
<label class="form-check-label" for="marks_status_absent">✗ Нет</label>
</div>
</div>
</div>
<!-- Coordinates Filters -->
<div class="mb-3">
<label class="form-label">Координаты геолокации:</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="has_geo_all" value=""
{% if not has_geo %}checked{% endif %}>
<label class="form-check-label" for="has_geo_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="has_geo_1" value="1"
{% if has_geo == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_geo_1">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_geo" id="has_geo_0" value="0"
{% if has_geo == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_geo_0">Нет</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Координаты Кубсата:</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="has_kupsat_all" value=""
{% if not has_kupsat %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="has_kupsat_1" value="1"
{% if has_kupsat == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_1">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_kupsat" id="has_kupsat_0" value="0"
{% if has_kupsat == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_0">Нет</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Координаты опер. отдела:</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="has_valid_all" value=""
{% if not has_valid %}checked{% endif %}>
<label class="form-check-label" for="has_valid_all">Все</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="has_valid_1" value="1"
{% if has_valid == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_1">Есть</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="has_valid" id="has_valid_0" value="0"
{% if has_valid == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_0">Нет</label>
</div>
</div>
</div>
<!-- Items Per Page -->
<div class="mb-3">
<label for="items-per-page" class="form-label">Элементов на странице:</label>
<select name="items_per_page" id="items-per-page" class="form-select form-select-sm">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
</div>
<!-- Apply Filters and Reset Buttons -->
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
<a href="?" class="btn btn-secondary btn-sm">Сбросить</a>
</div>
</form>
</div>
</div>
</div>
<!-- Main Content -->
<div class="col-md-9">
<div class="card h-100">
<div class="card-body p-0">
{% if display_mode == 'sources' %}
<!-- Sources Table -->
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col">ID</th>
<th scope="col">Спутники</th>
<th scope="col">Кол-во объектов</th>
<th scope="col">Средние координаты</th>
<th scope="col">Координаты Кубсата</th>
<th scope="col">Координаты опер. отдела</th>
<th scope="col">Дата создания</th>
</tr>
</thead>
<tbody>
{% for source in processed_sources %}
<tr>
<td><a href="{% url 'mainapp:source_update' source.id %}">{{ source.id }}</a></td>
<td>{{ source.satellites }}</td>
<td>{{ source.objitem_count }}</td>
<td>{{ source.coords_average }}</td>
<td>{{ source.coords_kupsat }}</td>
<td>{{ source.coords_valid }}</td>
<td>{{ source.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center py-4">
Нет данных для выбранных фильтров
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<!-- ObjItems Table -->
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col">ID</th>
<th scope="col">Имя</th>
<th scope="col">Спутник</th>
<th scope="col">Частота, МГц</th>
<th scope="col">Полоса, МГц</th>
<th scope="col">Поляризация</th>
<th scope="col">Модуляция</th>
<th scope="col">Сим. v</th>
<th scope="col">ОСШ</th>
<th scope="col">Геолокация</th>
<th scope="col">Дата гео</th>
<th scope="col">Кубсат</th>
<th scope="col">Опер. отд</th>
<th scope="col">Источник</th>
{% if show_marks == '1' %}
<th scope="col">Отметки</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for item in processed_objitems %}
<tr>
<td><a href="{% url 'mainapp:objitem_detail' item.id %}">{{ item.id }}</a></td>
<td>{{ item.name }}</td>
<td>{{ item.satellite }}</td>
<td>{{ item.frequency }}</td>
<td>{{ item.freq_range }}</td>
<td>{{ item.polarization }}</td>
<td>{{ item.modulation }}</td>
<td>{{ item.bod_velocity }}</td>
<td>{{ item.snr }}</td>
<td>{{ item.geo_coords }}</td>
<td>{{ item.geo_date }}</td>
<td>{{ item.kupsat_coords }}</td>
<td>{{ item.valid_coords }}</td>
<td>
{% if item.source_id %}
<a href="{% url 'mainapp:source_update' item.source_id %}">{{ item.source_id }}</a>
{% else %}
-
{% endif %}
</td>
{% if show_marks == '1' %}
<td>
{% if item.marks %}
<div class="marks-list">
{% for mark in item.marks %}
<div class="mark-item mb-1">
<span class="badge {% if mark.mark %}bg-success{% else %}bg-danger{% endif %}">
{% if mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
</span>
<small class="text-muted d-block">
{{ mark.timestamp|date:"d.m.Y H:i" }}
</small>
<small class="text-muted d-block">
{{ mark.created_by }}
</small>
</div>
{% endfor %}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="{% if show_marks == '1' %}15{% else %}14{% endif %}" class="text-center py-4">
Нет данных для выбранных фильтров
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Pagination -->
{% if page_obj.paginator.num_pages > 1 %}
<nav aria-label="Page navigation" class="px-3 pb-3">
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Предыдущая</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<!-- Pagination Info -->
{% if page_obj %}
<div class="px-3 pb-3 text-center">
<small class="text-muted">Показано {{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }} записей</small>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<style>
.marks-list {
max-height: 200px;
overflow-y: auto;
}
.mark-item {
padding: 4px;
border-bottom: 1px solid #e9ecef;
}
.mark-item:last-child {
border-bottom: none;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to select/deselect all options in a select element
window.selectAllOptions = function(selectName, selectAll) {
const selectElement = document.querySelector(`select[name="${selectName}"]`);
if (selectElement) {
for (let i = 0; i < selectElement.options.length; i++) {
selectElement.options[i].selected = selectAll;
}
}
};
// Toggle marks filters visibility
const showMarksRadios = document.querySelectorAll('input[name="show_marks"]');
const marksDateFilter = document.getElementById('marks-date-filter');
const marksStatusFilter = document.getElementById('marks-status-filter');
showMarksRadios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === '1') {
marksDateFilter.style.display = 'block';
marksStatusFilter.style.display = 'block';
} else {
marksDateFilter.style.display = 'none';
marksStatusFilter.style.display = 'none';
}
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,383 @@
{% extends "mainapp/base.html" %}
{% load static %}
{% block title %}Наличие сигнала объектов{% endblock %}
{% block extra_css %}
<style>
.marks-table {
width: 100%;
border-collapse: collapse;
}
.marks-table th,
.marks-table td {
border: 1px solid #dee2e6;
padding: 8px;
vertical-align: middle;
}
.marks-table th {
background-color: #f8f9fa;
font-weight: 600;
text-align: center;
}
.source-info-cell {
min-width: 250px;
background-color: #f8f9fa;
}
.marks-cell {
min-width: 150px;
text-align: center;
}
.actions-cell {
min-width: 180px;
text-align: center;
}
.mark-status {
font-size: 1.1rem;
}
.mark-present {
color: #28a745;
font-weight: 600;
}
.mark-absent {
color: #dc3545;
font-weight: 600;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
}
.btn-mark {
padding: 6px 16px;
font-size: 0.875rem;
min-width: 100px;
}
.btn-edit-mark {
padding: 2px 8px;
font-size: 0.75rem;
}
.filter-section {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.no-marks {
color: #6c757d;
font-style: italic;
text-align: center;
}
.btn-mark:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-edit-mark:disabled {
opacity: 0.5;
cursor: wait;
}
.mark-status {
transition: color 0.3s ease;
}
.btn-edit-mark:hover:not(:disabled) {
background-color: #6c757d;
color: white;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Наличие сигнала объектов</h2>
</div>
<!-- Фильтры -->
<div class="filter-section">
<form method="get" class="row g-3">
<div class="col-md-6">
<label for="satellite" class="form-label">Спутник</label>
<select class="form-select" id="satellite" name="satellite">
<option value="">Все спутники</option>
{% for sat in satellites %}
<option value="{{ sat.id }}" {% if request.GET.satellite == sat.id|stringformat:"s" %}selected{% endif %}>
{{ sat.name }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-6 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">Применить</button>
<a href="{% url 'mainapp:object_marks' %}" class="btn btn-secondary">Сбросить</a>
</div>
</form>
</div>
<!-- Таблица с наличие сигналами -->
<div class="table-responsive">
<table class="marks-table table table-bordered">
<thead>
<tr>
<th class="source-info-cell">Информация об объекте</th>
<th class="marks-cell">Наличие</th>
<th class="marks-cell">Дата и время</th>
<th class="actions-cell">Действия</th>
</tr>
</thead>
<tbody>
{% for source in sources %}
{% with marks=source.marks.all %}
{% if marks %}
<!-- Первая строка с информацией об объекте и первой отметкой -->
<tr data-source-id="{{ source.id }}">
<td class="source-info-cell" rowspan="{{ marks.count }}">
<div><strong>ID:</strong> {{ source.id }}</div>
<div><strong>Дата создания:</strong> {{ source.created_at|date:"d.m.Y H:i" }}</div>
<div><strong>Кол-во объектов:</strong> {{ source.source_objitems.count }}</div>
{% if source.coords_average %}
<div><strong>Усреднённые координаты:</strong> Есть</div>
{% endif %}
{% if source.coords_kupsat %}
<div><strong>Координаты Кубсата:</strong> Есть</div>
{% endif %}
{% if source.coords_valid %}
<div><strong>Координаты оперативников:</strong> Есть</div>
{% endif %}
</td>
{% with first_mark=marks.0 %}
<td class="marks-cell" data-mark-id="{{ first_mark.id }}">
<span class="mark-status {% if first_mark.mark %}mark-present{% else %}mark-absent{% endif %}">
{% if first_mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
</span>
{% if first_mark.can_edit %}
<button class="btn btn-sm btn-outline-secondary btn-edit-mark ms-2"
onclick="toggleMark({{ first_mark.id }}, {{ first_mark.mark|yesno:'true,false' }})">
</button>
{% endif %}
</td>
<td class="marks-cell">
<div>{{ first_mark.timestamp|date:"d.m.Y H:i" }}</div>
<small class="text-muted">{{ first_mark.created_by|default:"—" }}</small>
</td>
<td class="actions-cell" rowspan="{{ marks.count }}">
<div class="action-buttons" id="actions-{{ source.id }}">
<button class="btn btn-success btn-mark btn-sm"
onclick="addMark({{ source.id }}, true)">
✓ Есть
</button>
<button class="btn btn-danger btn-mark btn-sm"
onclick="addMark({{ source.id }}, false)">
✗ Нет
</button>
</div>
</td>
{% endwith %}
</tr>
<!-- Остальные наличие сигнала -->
{% for mark in marks|slice:"1:" %}
<tr data-source-id="{{ source.id }}">
<td class="marks-cell" data-mark-id="{{ mark.id }}">
<span class="mark-status {% if mark.mark %}mark-present{% else %}mark-absent{% endif %}">
{% if mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
</span>
{% if mark.can_edit %}
<button class="btn btn-sm btn-outline-secondary btn-edit-mark ms-2"
onclick="toggleMark({{ mark.id }}, {{ mark.mark|yesno:'true,false' }})">
</button>
{% endif %}
</td>
<td class="marks-cell">
<div>{{ mark.timestamp|date:"d.m.Y H:i" }}</div>
<small class="text-muted">{{ mark.created_by|default:"—" }}</small>
</td>
</tr>
{% endfor %}
{% else %}
<!-- Объект без отметок -->
<tr data-source-id="{{ source.id }}">
<td class="source-info-cell">
<div><strong>ID:</strong> {{ source.id }}</div>
<div><strong>Дата создания:</strong> {{ source.created_at|date:"d.m.Y H:i" }}</div>
<div><strong>Кол-во объектов:</strong> {{ source.source_objitems.count }}</div>
{% if source.coords_average %}
<div><strong>Усреднённые координаты:</strong> Есть</div>
{% endif %}
{% if source.coords_kupsat %}
<div><strong>Координаты Кубсата:</strong> Есть</div>
{% endif %}
{% if source.coords_valid %}
<div><strong>Координаты оперативников:</strong> Есть</div>
{% endif %}
</td>
<td colspan="2" class="no-marks">Отметок нет</td>
<td class="actions-cell">
<div class="action-buttons" id="actions-{{ source.id }}">
<button class="btn btn-success btn-mark btn-sm"
onclick="addMark({{ source.id }}, true)">
✓ Есть
</button>
<button class="btn btn-danger btn-mark btn-sm"
onclick="addMark({{ source.id }}, false)">
✗ Нет
</button>
</div>
</td>
</tr>
{% endif %}
{% endwith %}
{% empty %}
<tr>
<td colspan="4" class="text-center py-4">
<p class="text-muted mb-0">Объекти не найдены</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Пагинация -->
{% if is_paginated %}
<nav aria-label="Навигация по страницам" class="mt-4">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Предыдущая</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} из {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function addMark(sourceId, mark) {
// Отключить кнопки для этого объекта
const buttons = document.querySelectorAll(`#actions-${sourceId} button`);
buttons.forEach(btn => btn.disabled = true);
fetch("{% url 'mainapp:add_object_mark' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': '{{ csrf_token }}'
},
body: `source_id=${sourceId}&mark=${mark}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Перезагрузить страницу для обновления таблицы
location.reload();
} else {
// Включить кнопки обратно
buttons.forEach(btn => btn.disabled = false);
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
})
.catch(error => {
console.error('Error:', error);
buttons.forEach(btn => btn.disabled = false);
alert('Ошибка при добавлении наличие сигнала');
});
}
function toggleMark(markId, currentValue) {
const newValue = !currentValue;
const cell = document.querySelector(`td[data-mark-id="${markId}"]`);
const editBtn = cell.querySelector('.btn-edit-mark');
// Отключить кнопку редактирования на время запроса
if (editBtn) {
editBtn.disabled = true;
}
fetch("{% url 'mainapp:update_object_mark' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': '{{ csrf_token }}'
},
body: `mark_id=${markId}&mark=${newValue}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Обновить отображение наличие сигнала без перезагрузки страницы
const statusSpan = cell.querySelector('.mark-status');
if (data.mark.mark) {
statusSpan.textContent = '✓ Есть';
statusSpan.className = 'mark-status mark-present';
} else {
statusSpan.textContent = '✗ Нет';
statusSpan.className = 'mark-status mark-absent';
}
// Обновить значение в onclick для следующего переключения
if (editBtn) {
editBtn.setAttribute('onclick', `toggleMark(${markId}, ${data.mark.mark})`);
editBtn.disabled = false;
}
// Если больше нельзя редактировать, убрать кнопку
if (!data.mark.can_edit && editBtn) {
editBtn.remove();
}
} else {
// Включить кнопку обратно при ошибке
if (editBtn) {
editBtn.disabled = false;
}
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
})
.catch(error => {
console.error('Error:', error);
if (editBtn) {
editBtn.disabled = false;
}
alert('Ошибка при изменении наличие сигнала');
});
}
</script>
{% endblock %}

View File

@@ -12,7 +12,7 @@
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2>Список объектов</h2>
<h2>Список точек</h2>
</div>
</div>
@@ -207,7 +207,7 @@
<!-- Source Type Filter -->
<div class="mb-2">
<label class="form-label">Тип источника:</label>
<label class="form-label">Тип точки:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_source_type"
@@ -277,7 +277,7 @@
<div class="card h-100">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">
@@ -302,7 +302,7 @@
{% include 'mainapp/components/_table_header.html' with label="Комментарий" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Тип точки" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Sigma" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Зеркала" field="" sortable=False %}
</tr>
@@ -1036,7 +1036,7 @@
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="lyngsatModalLabel">
<i class="bi bi-tv"></i> Данные источника LyngSat
<i class="bi bi-tv"></i> Данные объекта LyngSat
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Закрыть"></button>
@@ -1160,7 +1160,7 @@
<div class="col-md-6">
${data.url ? `
<p class="mb-2">
<span class="text-muted">Ссылка на источник:</span><br>
<span class="text-muted">Ссылка на объект:</span><br>
<a href="${data.url}" target="_blank" class="btn btn-sm btn-outline-primary">
<i class="bi bi-link-45deg"></i> Открыть на LyngSat
</a>

View File

@@ -30,7 +30,7 @@
<h5 class="mt-4 mb-3">Детали удаления:</h5>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-striped table-hover table-sm">
<table class="table table-striped table-hover table-sm table-bordered">
<thead class="table-dark sticky-top">
<tr>
<th class="text-center" style="width: 15%;">ID источника</th>

View File

@@ -1,7 +1,7 @@
{% extends 'mainapp/base.html' %}
{% load static %}
{% block title %}Удалить источник #{{ object.id }}{% endblock %}
{% block title %}Удалить объект #{{ object.id }}{% endblock %}
{% block content %}
<div class="container mt-5">
@@ -14,10 +14,10 @@
<div class="card-body">
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle-fill"></i>
<strong>Внимание!</strong> Вы собираетесь удалить источник.
<strong>Внимание!</strong> Вы собираетесь удалить объект.
</div>
<h5>Информация об источнике:</h5>
<h5>Информация об объекте:</h5>
<ul class="list-group mb-3">
<li class="list-group-item">
<strong>ID:</strong> {{ object.id }}
@@ -39,7 +39,7 @@
{% if objitems_count > 0 %}
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-circle-fill"></i>
<strong>Важно!</strong> При удалении источника будут также удалены все {{ objitems_count }} привязанных объектов!
<strong>Важно!</strong> При удалении объекта будут также удалены все {{ objitems_count }} привязанных объектов!
</div>
{% endif %}

View File

@@ -3,7 +3,7 @@
{% load static leaflet_tags %}
{% load l10n %}
{% block title %}Редактировать источник #{{ object.id }}{% endblock %}
{% block title %}Редактировать объект #{{ object.id }}{% endblock %}
{% block extra_css %}
<style>
@@ -129,7 +129,7 @@
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12 d-flex justify-content-between align-items-center">
<h2>Редактировать источник #{{ object.id }}</h2>
<h2>Редактировать объект #{{ object.id }}</h2>
<div>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="submit" form="source-form" class="btn btn-primary btn-action">Сохранить</button>
@@ -153,7 +153,7 @@
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">ID источника:</label>
<label class="form-label">ID объекта:</label>
<div class="readonly-field">{{ object.id }}</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
{% extends 'mainapp/base.html' %}
{% block title %}Список источников{% endblock %}
{% block title %}Список объектов{% endblock %}
{% block extra_css %}
<style>
@@ -29,7 +29,7 @@
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2>Список источников</h2>
<h2>Список объектов</h2>
</div>
</div>
@@ -194,7 +194,7 @@
<!-- LyngSat Filter -->
<div class="mb-2">
<label class="form-label">Тип источника (ТВ):</label>
<label class="form-label">Тип объекта (ТВ):</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_1"
@@ -242,7 +242,7 @@
<div class="card">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">
@@ -263,8 +263,9 @@
<th scope="col" style="min-width: 150px;">Координаты Кубсата</th>
<th scope="col" style="min-width: 150px;">Координаты оперативников</th>
<th scope="col" style="min-width: 150px;">Координаты справочные</th>
<th scope="col" style="min-width: 180px;">Наличие сигнала</th>
{% if has_any_lyngsat %}
<th scope="col" class="text-center" style="min-width: 80px;">Тип источника</th>
<th scope="col" class="text-center" style="min-width: 80px;">Тип объекта</th>
{% endif %}
<th scope="col" class="text-center" style="min-width: 100px;">
<a href="javascript:void(0)" onclick="updateSort('objitem_count')" class="text-white text-decoration-none">
@@ -312,6 +313,29 @@
<td>{{ source.coords_kupsat }}</td>
<td>{{ source.coords_valid }}</td>
<td>{{ source.coords_reference }}</td>
<td style="padding: 0.3rem; vertical-align: top;">
{% if source.marks %}
<div style="font-size: 0.75rem; line-height: 1.3;">
{% for mark in source.marks %}
<div style="{% if not forloop.last %}border-bottom: 1px solid #dee2e6; padding-bottom: 3px; margin-bottom: 3px;{% endif %}">
<div style="margin-bottom: 1px;">
{% if mark.mark %}
<span class="badge bg-success" style="font-size: 0.7rem;">Есть</span>
{% elif mark.mark == False %}
<span class="badge bg-danger" style="font-size: 0.7rem;">Нет</span>
{% else %}
<span class="badge bg-secondary" style="font-size: 0.7rem;">-</span>
{% endif %}
<span class="text-muted" style="font-size: 0.7rem;">{{ mark.timestamp|date:"d.m.y H:i" }}</span>
</div>
<div class="text-muted" style="font-size: 0.65rem;">{{ mark.created_by }}</div>
</div>
{% endfor %}
</div>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
{% if has_any_lyngsat %}
<td class="text-center">
{% if source.has_lyngsat %}
@@ -333,7 +357,7 @@
<a href="{% url 'mainapp:show_source_with_points_map' source.id %}"
target="_blank"
class="btn btn-sm btn-outline-success"
title="Показать источник с точками на карте">
title="Показать объект с точками на карте">
<i class="bi bi-geo-alt"></i>
<span class="badge bg-success">{{ source.objitem_count }}</span>
</a>
@@ -365,7 +389,7 @@
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<a href="{% url 'mainapp:source_update' source.id %}"
class="btn btn-sm btn-outline-warning"
title="Редактировать источник">
title="Редактировать объект">
<i class="bi bi-pencil"></i>
</a>
{% else %}
@@ -378,7 +402,7 @@
</tr>
{% empty %}
<tr>
<td colspan="11" class="text-center text-muted">Нет данных для отображения</td>
<td colspan="12" class="text-center text-muted">Нет данных для отображения</td>
</tr>
{% endfor %}
</tbody>
@@ -395,7 +419,7 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали источника #<span id="modalSourceId"></span></h5>
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали объекта #<span id="modalSourceId"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
@@ -406,6 +430,25 @@
</div>
<div id="modalErrorMessage" class="alert alert-danger" style="display: none;"></div>
<div id="modalContent" style="display: none;">
<!-- Marks Section -->
<div id="marksSection" class="mb-3" style="display: none;">
<h6 class="mb-2">Наличие сигнала объекта (<span id="marksCount">0</span>):</h6>
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr>
<th style="width: 20%;">Наличие сигнала</th>
<th style="width: 40%;">Дата и время</th>
<th style="width: 40%;">Пользователь</th>
</tr>
</thead>
<tbody id="marksTableBody">
<!-- Marks will be loaded here -->
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Связанные точки (<span id="objitemCount">0</span>):</h6>
<div class="dropdown">
@@ -441,14 +484,14 @@
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="18" onchange="toggleModalColumn(this)"> Комментарий</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="19" onchange="toggleModalColumn(this)"> Усреднённое</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="20" onchange="toggleModalColumn(this)"> Стандарт</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="21" checked onchange="toggleModalColumn(this)"> Тип источника</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="21" checked onchange="toggleModalColumn(this)"> Тип объекта</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="22" onchange="toggleModalColumn(this)"> Sigma</label></li>
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="23" checked onchange="toggleModalColumn(this)"> Зеркала</label></li>
</ul>
</div>
</div>
<div class="table-responsive" style="max-height: 80vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-light sticky-top">
<tr>
<th class="text-center" style="width: 3%;">
@@ -474,7 +517,7 @@
<th style="min-width: 150px;">Комментарий</th>
<th style="min-width: 100px;">Усреднённое</th>
<th style="min-width: 100px;">Стандарт</th>
<th style="min-width: 100px;">Тип источника</th>
<th style="min-width: 100px;">Тип объекта</th>
<th style="min-width: 80px;">Sigma</th>
<th style="min-width: 80px;">Зеркала</th>
</tr>
@@ -534,7 +577,7 @@ function showSelectedOnMap() {
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один источник для отображения на карте');
alert('Пожалуйста, выберите хотя бы один объект для отображения на карте');
return;
}
@@ -555,7 +598,7 @@ function deleteSelectedSources() {
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один источник для удаления');
alert('Пожалуйста, выберите хотя бы один объект для удаления');
return;
}
@@ -774,7 +817,7 @@ function showSourceDetails(sourceId) {
.then(response => {
if (!response.ok) {
if (response.status === 404) {
throw new Error('Источник не найден');
throw new Error('Объект не найден');
} else {
throw new Error('Ошибка при загрузке данных');
}
@@ -785,6 +828,33 @@ function showSourceDetails(sourceId) {
// Hide loading spinner
document.getElementById('modalLoadingSpinner').style.display = 'none';
// Show marks if available
if (data.marks && data.marks.length > 0) {
document.getElementById('marksSection').style.display = 'block';
document.getElementById('marksCount').textContent = data.marks.length;
const marksTableBody = document.getElementById('marksTableBody');
marksTableBody.innerHTML = '';
data.marks.forEach(mark => {
const row = document.createElement('tr');
let markBadge = '<span class="badge bg-secondary">-</span>';
if (mark.mark === true) {
markBadge = '<span class="badge bg-success">Есть</span>';
} else if (mark.mark === false) {
markBadge = '<span class="badge bg-danger">Нет</span>';
}
row.innerHTML = '<td class="text-center">' + markBadge + '</td>' +
'<td>' + mark.timestamp + '</td>' +
'<td>' + mark.created_by + '</td>';
marksTableBody.appendChild(row);
});
} else {
document.getElementById('marksSection').style.display = 'none';
}
if (data.objitems && data.objitems.length > 0) {
// Show content
document.getElementById('modalContent').style.display = 'block';
@@ -981,7 +1051,7 @@ function showLyngsatModal(lyngsatId) {
'<div class="card-header bg-light"><strong><i class="bi bi-clock-history"></i> Дополнительная информация</strong></div>' +
'<div class="card-body"><div class="row">' +
'<div class="col-md-6"><p class="mb-2"><span class="text-muted">Последнее обновление:</span><br><strong>' + data.last_update + '</strong></p></div>' +
'<div class="col-md-6">' + (data.url ? '<p class="mb-2"><span class="text-muted">Ссылка на источник:</span><br>' +
'<div class="col-md-6">' + (data.url ? '<p class="mb-2"><span class="text-muted">Ссылка на объект:</span><br>' +
'<a href="' + data.url + '" target="_blank" class="btn btn-sm btn-outline-primary">' +
'<i class="bi bi-link-45deg"></i> Открыть на LyngSat</a></p>' : '') +
'</div></div></div></div></div></div></div>';
@@ -1048,7 +1118,7 @@ function showTransponderModal(transponderId) {
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="lyngsatModalLabel">
<i class="bi bi-tv"></i> Данные источника LyngSat
<i class="bi bi-tv"></i> Данные объекта LyngSat
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Закрыть"></button>

View File

@@ -1,6 +1,6 @@
{% extends "mainapp/base.html" %}
{% load static %}
{% block title %}Карта источников{% endblock title %}
{% block title %}Карта объектов{% endblock title %}
{% block extra_css %}
<!-- Leaflet CSS -->

View File

@@ -1,6 +1,6 @@
{% extends "mainapp/base.html" %}
{% load static %}
{% block title %}Карта источника #{{ source_id }} с точками{% endblock title %}
{% block title %}Карта объекта #{{ source_id }} с точками{% endblock title %}
{% block extra_css %}
<!-- Leaflet CSS -->
@@ -120,7 +120,7 @@
var sourceOverlays = [];
var glPointLayers = [];
// Создаём слои для координат источника и точек ГЛ
// Создаём слои для координат объекта и точек ГЛ
{% for group in groups %}
var groupName = '{{ group.name|escapejs }}';
var colorName = '{{ group.color }}';
@@ -129,7 +129,7 @@
{% for point_data in group.points %}
{% if point_data.source_id %}
// Это координата источника
// Это координата объекта
var pointName = "{{ point_data.source_id|escapejs }}";
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
icon: groupIcon
@@ -151,7 +151,7 @@
{% endif %}
{% endfor %}
// Для координат источника добавляем как отдельный слой без вложенности
// Для координат объекта добавляем как отдельный слой без вложенности
{% if group.color in 'blue,orange,green,violet' %}
sourceOverlays.push({
label: groupName,
@@ -165,7 +165,7 @@
if (sourceOverlays.length > 0) {
treeOverlays.push({
label: "Координаты источника #{{ source_id }}",
label: "Координаты объекта #{{ source_id }}",
selectAllCheckbox: true,
children: sourceOverlays,
layer: L.layerGroup()
@@ -205,13 +205,13 @@
var div = L.DomUtil.create('div', 'legend');
div.innerHTML = '<h6><strong>Легенда</strong></h6>';
// Координаты источника
// Координаты объекта
var hasSourceCoords = false;
{% for group in groups %}
{% if group.color in 'blue,orange,green,violet' %}
{% if not hasSourceCoords %}
if (!hasSourceCoords) {
div.innerHTML += '<div class="legend-section-title">Координаты источника:</div>';
div.innerHTML += '<div class="legend-section-title">Координаты объекта:</div>';
hasSourceCoords = true;
}
{% endif %}

View File

@@ -30,7 +30,7 @@
<h5 class="mt-4 mb-3">Детали удаления:</h5>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-striped table-hover table-sm">
<table class="table table-striped table-hover table-sm table-bordered">
<thead class="table-dark sticky-top">
<tr>
<th class="text-center" style="width: 10%;">ID</th>

View File

@@ -191,7 +191,7 @@
<div class="card">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">