1194 lines
66 KiB
HTML
1194 lines
66 KiB
HTML
{% extends 'mainapp/base.html' %}
|
||
|
||
{% block title %}Список объектов{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.table-responsive tr.selected {
|
||
background-color: #d4edff;
|
||
}
|
||
.sticky-top {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
.btn-group .badge {
|
||
position: absolute;
|
||
top: -5px;
|
||
right: -5px;
|
||
font-size: 0.65rem;
|
||
padding: 0.2em 0.4em;
|
||
}
|
||
.btn-group .btn {
|
||
position: relative;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid px-3">
|
||
<div class="row mb-3">
|
||
<div class="col-12">
|
||
<h2>Список объектов</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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">
|
||
<!-- Search bar -->
|
||
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
|
||
<div class="input-group">
|
||
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по ID..."
|
||
value="{{ search_query|default:'' }}">
|
||
<button type="button" class="btn btn-outline-primary"
|
||
onclick="performSearch()">Найти</button>
|
||
<button type="button" class="btn btn-outline-secondary"
|
||
onclick="clearSearch()">Очистить</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Items per page select -->
|
||
<div>
|
||
<label for="items-per-page" class="form-label mb-0">Показать:</label>
|
||
<select name="items_per_page" id="items-per-page"
|
||
class="form-select form-select-sm d-inline-block" style="width: auto;"
|
||
onchange="updateItemsPerPage()">
|
||
{% for option in available_items_per_page %}
|
||
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
|
||
{{ option }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Action buttons -->
|
||
<div class="d-flex gap-2">
|
||
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
|
||
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
|
||
onclick="deleteSelectedSources()">
|
||
<i class="bi bi-trash"></i> Удалить
|
||
</button>
|
||
{% endif %}
|
||
<button type="button" class="btn btn-outline-primary btn-sm" title="Показать на карте"
|
||
onclick="showSelectedOnMap()">
|
||
<i class="bi bi-map"></i> Карта
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Filter Toggle Button -->
|
||
<div>
|
||
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
|
||
data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters">
|
||
<i class="bi bi-funnel"></i> Фильтры
|
||
<span id="filterCounter" class="badge bg-danger" style="display: none;">0</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
<div class="ms-auto">
|
||
{% include 'mainapp/components/_pagination.html' with page_obj=page_obj show_info=True %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Offcanvas Filter Panel -->
|
||
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasFilters" aria-labelledby="offcanvasFiltersLabel">
|
||
<div class="offcanvas-header">
|
||
<h5 class="offcanvas-title" id="offcanvasFiltersLabel">Фильтры</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
<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>
|
||
<select name="satellite_id" class="form-select form-select-sm mb-2" multiple size="6">
|
||
{% for satellite in satellites %}
|
||
<option value="{{ satellite.id }}" {% if satellite.id in selected_satellites %}selected{% endif %}>
|
||
{{ satellite.name }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Coordinates Average 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_coords_average" id="has_coords_average_1"
|
||
value="1" {% if has_coords_average == '1' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_average_1">Есть</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" name="has_coords_average" id="has_coords_average_0"
|
||
value="0" {% if has_coords_average == '0' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_average_0">Нет</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kubsat 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_coords_kupsat" id="has_coords_kupsat_1"
|
||
value="1" {% if has_coords_kupsat == '1' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_kupsat_1">Есть</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" name="has_coords_kupsat" id="has_coords_kupsat_0"
|
||
value="0" {% if has_coords_kupsat == '0' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_kupsat_0">Нет</label>
|
||
</div>
|
||
</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_coords_valid" id="has_coords_valid_1"
|
||
value="1" {% if has_coords_valid == '1' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_valid_1">Есть</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" name="has_coords_valid" id="has_coords_valid_0"
|
||
value="0" {% if has_coords_valid == '0' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_valid_0">Нет</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Reference 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_coords_reference" id="has_coords_reference_1"
|
||
value="1" {% if has_coords_reference == '1' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_reference_1">Есть</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" name="has_coords_reference" id="has_coords_reference_0"
|
||
value="0" {% if has_coords_reference == '0' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_coords_reference_0">Нет</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LyngSat 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_lyngsat" id="has_lyngsat_1"
|
||
value="1" {% if has_lyngsat == '1' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_lyngsat_1">Есть</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_0"
|
||
value="0" {% if has_lyngsat == '0' %}checked{% endif %}>
|
||
<label class="form-check-label" for="has_lyngsat_0">Нет</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Point Count Filter -->
|
||
<div class="mb-2">
|
||
<label class="form-label">Количество точек:</label>
|
||
<input type="number" name="objitem_count_min" class="form-control form-control-sm mb-1"
|
||
placeholder="От" value="{{ objitem_count_min|default:'' }}">
|
||
<input type="number" name="objitem_count_max" class="form-control form-control-sm"
|
||
placeholder="До" value="{{ objitem_count_max|default:'' }}">
|
||
</div>
|
||
|
||
<!-- Date Filter -->
|
||
<div class="mb-2">
|
||
<label class="form-label">Дата создания:</label>
|
||
<input type="date" name="date_from" id="date_from" class="form-control form-control-sm mb-1"
|
||
placeholder="От" value="{{ date_from|default:'' }}">
|
||
<input type="date" name="date_to" id="date_to" class="form-control form-control-sm"
|
||
placeholder="До" value="{{ date_to|default:'' }}">
|
||
</div>
|
||
|
||
<!-- Geo Timestamp Filter -->
|
||
<div class="mb-2">
|
||
<label class="form-label">Дата ГЛ:</label>
|
||
<input type="date" name="geo_date_from" id="geo_date_from" class="form-control form-control-sm mb-1"
|
||
placeholder="От" value="{{ geo_date_from|default:'' }}">
|
||
<input type="date" name="geo_date_to" id="geo_date_to" class="form-control form-control-sm"
|
||
placeholder="До" value="{{ geo_date_to|default:'' }}">
|
||
</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>
|
||
|
||
<!-- Main Table -->
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<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 table-bordered" 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" class="text-center" style="min-width: 60px;">
|
||
<a href="javascript:void(0)" onclick="updateSort('id')" class="text-white text-decoration-none">
|
||
ID
|
||
{% if sort == 'id' %}
|
||
<i class="bi bi-arrow-up"></i>
|
||
{% elif sort == '-id' %}
|
||
<i class="bi bi-arrow-down"></i>
|
||
{% endif %}
|
||
</a>
|
||
</th>
|
||
<th scope="col" style="min-width: 120px;">Спутник</th>
|
||
<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: 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>
|
||
{% 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">
|
||
Кол-во точек
|
||
{% if sort == 'objitem_count' %}
|
||
<i class="bi bi-arrow-up"></i>
|
||
{% elif sort == '-objitem_count' %}
|
||
<i class="bi bi-arrow-down"></i>
|
||
{% endif %}
|
||
</a>
|
||
</th>
|
||
<th scope="col" style="min-width: 120px;">
|
||
<a href="javascript:void(0)" onclick="updateSort('created_at')" class="text-white text-decoration-none">
|
||
Создано
|
||
{% if sort == 'created_at' %}
|
||
<i class="bi bi-arrow-up"></i>
|
||
{% elif sort == '-created_at' %}
|
||
<i class="bi bi-arrow-down"></i>
|
||
{% endif %}
|
||
</a>
|
||
</th>
|
||
<th scope="col" style="min-width: 120px;">
|
||
<a href="javascript:void(0)" onclick="updateSort('updated_at')" class="text-white text-decoration-none">
|
||
Обновлено
|
||
{% if sort == 'updated_at' %}
|
||
<i class="bi bi-arrow-up"></i>
|
||
{% elif sort == '-updated_at' %}
|
||
<i class="bi bi-arrow-down"></i>
|
||
{% endif %}
|
||
</a>
|
||
</th>
|
||
<th scope="col" class="text-center" style="min-width: 150px;">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for source in processed_sources %}
|
||
<tr>
|
||
<td class="text-center">
|
||
<input type="checkbox" class="form-check-input item-checkbox"
|
||
value="{{ source.id }}">
|
||
</td>
|
||
<td class="text-center">{{ source.id }}</td>
|
||
<td>{{ source.satellite }}</td>
|
||
<td>{{ source.coords_average }}</td>
|
||
<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 %}
|
||
<a href="#" class="text-primary text-decoration-none"
|
||
onclick="showLyngsatModal({{ source.lyngsat_id }}); return false;">
|
||
<i class="bi bi-tv"></i> ТВ
|
||
</a>
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
<td class="text-center">{{ source.objitem_count }}</td>
|
||
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
|
||
<td>{{ source.updated_at|date:"d.m.Y H:i" }}</td>
|
||
<td class="text-center">
|
||
<div class="btn-group" role="group">
|
||
{% if source.objitem_count > 0 %}
|
||
<a href="{% url 'mainapp:show_source_with_points_map' source.id %}"
|
||
target="_blank"
|
||
class="btn btn-sm btn-outline-success"
|
||
title="Показать объект с точками на карте">
|
||
<i class="bi bi-geo-alt"></i>
|
||
<span class="badge bg-success">{{ source.objitem_count }}</span>
|
||
</a>
|
||
{% else %}
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="Нет точек для отображения">
|
||
<i class="bi bi-geo-alt"></i>
|
||
</button>
|
||
{% endif %}
|
||
|
||
{% if source.objitem_count > 1 %}
|
||
<a href="{% url 'mainapp:show_source_averaging_map' source.id %}"
|
||
target="_blank"
|
||
class="btn btn-sm btn-outline-info"
|
||
title="Визуализация усреднения">
|
||
<i class="bi bi-diagram-3"></i>
|
||
</a>
|
||
{% else %}
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="Недостаточно точек для усреднения">
|
||
<i class="bi bi-diagram-3"></i>
|
||
</button>
|
||
{% endif %}
|
||
|
||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||
onclick="showSourceDetails({{ source.id }})"
|
||
title="Показать детали">
|
||
<i class="bi bi-eye"></i>
|
||
</button>
|
||
|
||
{% 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="Редактировать объект">
|
||
<i class="bi bi-pencil"></i>
|
||
</a>
|
||
{% else %}
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="Недостаточно прав">
|
||
<i class="bi bi-pencil"></i>
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr>
|
||
<td colspan="12" class="text-center text-muted">Нет данных для отображения</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal for Source Details -->
|
||
<div class="modal fade" id="sourceDetailsModal" tabindex="-1" aria-labelledby="sourceDetailsModalLabel" aria-hidden="true">
|
||
<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>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="modalLoadingSpinner" class="text-center py-4">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">Загрузка...</span>
|
||
</div>
|
||
</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">
|
||
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
||
id="modalColumnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||
<i class="bi bi-gear"></i> Колонки
|
||
</button>
|
||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="modalColumnVisibilityDropdown" style="max-height: 400px; overflow-y: auto;">
|
||
<li>
|
||
<label class="dropdown-item">
|
||
<input type="checkbox" id="modal-select-all-columns" onchange="toggleAllModalColumns(this)"> Выбрать всё
|
||
</label>
|
||
</li>
|
||
<li><hr class="dropdown-divider"></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="0" checked onchange="toggleModalColumn(this)"> Выбрать</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="1" checked onchange="toggleModalColumn(this)"> ID</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="2" checked onchange="toggleModalColumn(this)"> Имя</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="3" checked onchange="toggleModalColumn(this)"> Спутник</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="4" checked onchange="toggleModalColumn(this)"> Транспондер</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="5" checked onchange="toggleModalColumn(this)"> Частота, МГц</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="6" checked onchange="toggleModalColumn(this)"> Полоса, МГц</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="7" checked onchange="toggleModalColumn(this)"> Поляризация</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="8" checked onchange="toggleModalColumn(this)"> Сим. V</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="9" checked onchange="toggleModalColumn(this)"> Модул</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="10" checked onchange="toggleModalColumn(this)"> ОСШ</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="11" checked onchange="toggleModalColumn(this)"> Время ГЛ</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="12" checked onchange="toggleModalColumn(this)"> Местоположение</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="13" checked onchange="toggleModalColumn(this)"> Геолокация</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="14" checked onchange="toggleModalColumn(this)"> Обновлено</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="15" checked onchange="toggleModalColumn(this)"> Кем(обн)</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="16" onchange="toggleModalColumn(this)"> Создано</label></li>
|
||
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="17" onchange="toggleModalColumn(this)"> Кем(созд)</label></li>
|
||
<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="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 table-bordered" style="font-size: 0.85rem;">
|
||
<thead class="table-light sticky-top">
|
||
<tr>
|
||
<th class="text-center" style="width: 3%;">
|
||
<input type="checkbox" id="modal-select-all" class="form-check-input">
|
||
</th>
|
||
<th class="text-center" style="min-width: 60px;">ID</th>
|
||
<th style="min-width: 120px;">Имя</th>
|
||
<th style="min-width: 120px;">Спутник</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: 100px;">Сим. V</th>
|
||
<th style="min-width: 100px;">Модул</th>
|
||
<th style="min-width: 80px;">ОСШ</th>
|
||
<th style="min-width: 120px;">Время ГЛ</th>
|
||
<th style="min-width: 120px;">Местоположение</th>
|
||
<th style="min-width: 120px;">Геолокация</th>
|
||
<th style="min-width: 120px;">Обновлено</th>
|
||
<th style="min-width: 100px;">Кем(обн)</th>
|
||
<th style="min-width: 120px;">Создано</th>
|
||
<th style="min-width: 100px;">Кем(созд)</th>
|
||
<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: 80px;">Sigma</th>
|
||
<th style="min-width: 80px;">Зеркала</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="objitemTableBody">
|
||
<!-- Data will be loaded here via AJAX -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div id="modalNoData" class="text-center text-muted py-4" style="display: none;">
|
||
Нет связанных точек
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
let lastCheckedIndex = null;
|
||
|
||
function updateRowHighlight(checkbox) {
|
||
const row = checkbox.closest('tr');
|
||
if (checkbox.checked) {
|
||
row.classList.add('selected');
|
||
} else {
|
||
row.classList.remove('selected');
|
||
}
|
||
}
|
||
|
||
function handleCheckboxClick(e) {
|
||
if (e.shiftKey && lastCheckedIndex !== null) {
|
||
const checkboxes = document.querySelectorAll('.item-checkbox');
|
||
const currentIndex = Array.from(checkboxes).indexOf(e.target);
|
||
const startIndex = Math.min(lastCheckedIndex, currentIndex);
|
||
const endIndex = Math.max(lastCheckedIndex, currentIndex);
|
||
|
||
for (let i = startIndex; i <= endIndex; i++) {
|
||
checkboxes[i].checked = e.target.checked;
|
||
updateRowHighlight(checkboxes[i]);
|
||
}
|
||
} else {
|
||
updateRowHighlight(e.target);
|
||
}
|
||
lastCheckedIndex = Array.from(document.querySelectorAll('.item-checkbox')).indexOf(e.target);
|
||
}
|
||
|
||
// Function to show selected sources on map
|
||
function showSelectedOnMap() {
|
||
// Get all checked checkboxes
|
||
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
|
||
|
||
if (checkedCheckboxes.length === 0) {
|
||
alert('Пожалуйста, выберите хотя бы один объект для отображения на карте');
|
||
return;
|
||
}
|
||
|
||
// Extract IDs from checked checkboxes
|
||
const selectedIds = [];
|
||
checkedCheckboxes.forEach(checkbox => {
|
||
selectedIds.push(checkbox.value);
|
||
});
|
||
|
||
// Redirect to the map view with selected IDs as query parameter
|
||
const url = '{% url "mainapp:show_sources_map" %}' + '?ids=' + selectedIds.join(',');
|
||
window.open(url, '_blank'); // Open in a new tab
|
||
}
|
||
|
||
// Function to delete selected sources
|
||
function deleteSelectedSources() {
|
||
// Get all checked checkboxes
|
||
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
|
||
|
||
if (checkedCheckboxes.length === 0) {
|
||
alert('Пожалуйста, выберите хотя бы один объект для удаления');
|
||
return;
|
||
}
|
||
|
||
// Extract IDs from checked checkboxes
|
||
const selectedIds = [];
|
||
checkedCheckboxes.forEach(checkbox => {
|
||
selectedIds.push(checkbox.value);
|
||
});
|
||
|
||
// Redirect to confirmation page
|
||
const url = '{% url "mainapp:delete_selected_sources" %}' + '?ids=' + selectedIds.join(',');
|
||
window.location.href = url;
|
||
}
|
||
|
||
// Search functionality
|
||
function performSearch() {
|
||
const searchValue = document.getElementById('toolbar-search').value.trim();
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
|
||
if (searchValue) {
|
||
urlParams.set('search', searchValue);
|
||
} else {
|
||
urlParams.delete('search');
|
||
}
|
||
|
||
urlParams.delete('page');
|
||
window.location.search = urlParams.toString();
|
||
}
|
||
|
||
function clearSearch() {
|
||
document.getElementById('toolbar-search').value = '';
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
urlParams.delete('search');
|
||
urlParams.delete('page');
|
||
window.location.search = urlParams.toString();
|
||
}
|
||
|
||
// Handle Enter key in search input
|
||
document.getElementById('toolbar-search').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
performSearch();
|
||
}
|
||
});
|
||
|
||
// Items per page functionality
|
||
function updateItemsPerPage() {
|
||
const itemsPerPage = document.getElementById('items-per-page').value;
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
urlParams.set('items_per_page', itemsPerPage);
|
||
urlParams.delete('page');
|
||
window.location.search = urlParams.toString();
|
||
}
|
||
|
||
// Sorting functionality
|
||
function updateSort(field) {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const currentSort = urlParams.get('sort');
|
||
|
||
let newSort;
|
||
if (currentSort === field) {
|
||
newSort = '-' + field;
|
||
} else if (currentSort === '-' + field) {
|
||
newSort = field;
|
||
} else {
|
||
newSort = field;
|
||
}
|
||
|
||
urlParams.set('sort', newSort);
|
||
urlParams.delete('page');
|
||
window.location.search = urlParams.toString();
|
||
}
|
||
|
||
// Setup radio-like behavior for filter checkboxes
|
||
function setupRadioLikeCheckboxes(name) {
|
||
const checkboxes = document.querySelectorAll(`input[name="${name}"]`);
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.addEventListener('change', function () {
|
||
if (this.checked) {
|
||
checkboxes.forEach(other => {
|
||
if (other !== this) {
|
||
other.checked = false;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Function to select/deselect all options in a select element
|
||
function selectAllOptions(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;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Filter counter functionality
|
||
function updateFilterCounter() {
|
||
const form = document.getElementById('filter-form');
|
||
const formData = new FormData(form);
|
||
let filterCount = 0;
|
||
|
||
// Count non-empty form fields
|
||
for (const [key, value] of formData.entries()) {
|
||
if (value && value.trim() !== '') {
|
||
// For multi-select fields, skip counting individual selections
|
||
if (key === 'satellite_id') {
|
||
continue;
|
||
}
|
||
filterCount++;
|
||
}
|
||
}
|
||
|
||
// Count selected options in satellite multi-select field
|
||
const satelliteSelect = document.querySelector('select[name="satellite_id"]');
|
||
if (satelliteSelect) {
|
||
const selectedOptions = Array.from(satelliteSelect.selectedOptions).filter(opt => opt.selected);
|
||
if (selectedOptions.length > 0) {
|
||
filterCount++;
|
||
}
|
||
}
|
||
|
||
// Display the filter counter
|
||
const counterElement = document.getElementById('filterCounter');
|
||
if (counterElement) {
|
||
if (filterCount > 0) {
|
||
counterElement.textContent = filterCount;
|
||
counterElement.style.display = 'inline';
|
||
} else {
|
||
counterElement.style.display = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Initialize on page load
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Setup select-all checkbox
|
||
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;
|
||
updateRowHighlight(checkbox);
|
||
});
|
||
});
|
||
|
||
itemCheckboxes.forEach(checkbox => {
|
||
checkbox.addEventListener('change', function () {
|
||
const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked);
|
||
selectAllCheckbox.checked = allChecked;
|
||
});
|
||
|
||
// Add shift-click handler
|
||
checkbox.addEventListener('click', handleCheckboxClick);
|
||
});
|
||
}
|
||
|
||
// Setup radio-like checkboxes for filters
|
||
setupRadioLikeCheckboxes('has_coords_average');
|
||
setupRadioLikeCheckboxes('has_coords_kupsat');
|
||
setupRadioLikeCheckboxes('has_coords_valid');
|
||
setupRadioLikeCheckboxes('has_coords_reference');
|
||
setupRadioLikeCheckboxes('has_lyngsat');
|
||
|
||
// Update filter counter on page load
|
||
updateFilterCounter();
|
||
|
||
// Add event listeners to form elements to update counter when filters change
|
||
const form = document.getElementById('filter-form');
|
||
if (form) {
|
||
const inputFields = form.querySelectorAll('input[type="text"], input[type="number"], input[type="date"]');
|
||
inputFields.forEach(input => {
|
||
input.addEventListener('input', updateFilterCounter);
|
||
input.addEventListener('change', updateFilterCounter);
|
||
});
|
||
|
||
const selectFields = form.querySelectorAll('select');
|
||
selectFields.forEach(select => {
|
||
select.addEventListener('change', updateFilterCounter);
|
||
});
|
||
|
||
const checkboxFields = form.querySelectorAll('input[type="checkbox"]');
|
||
checkboxFields.forEach(checkbox => {
|
||
checkbox.addEventListener('change', updateFilterCounter);
|
||
});
|
||
}
|
||
|
||
// Update counter when offcanvas is shown
|
||
const offcanvasElement = document.getElementById('offcanvasFilters');
|
||
if (offcanvasElement) {
|
||
offcanvasElement.addEventListener('show.bs.offcanvas', updateFilterCounter);
|
||
}
|
||
});
|
||
|
||
// Show source details in modal
|
||
function showSourceDetails(sourceId) {
|
||
// Update modal title
|
||
document.getElementById('modalSourceId').textContent = sourceId;
|
||
|
||
// Show loading spinner, hide content and error
|
||
document.getElementById('modalLoadingSpinner').style.display = 'block';
|
||
document.getElementById('modalContent').style.display = 'none';
|
||
document.getElementById('modalNoData').style.display = 'none';
|
||
document.getElementById('modalErrorMessage').style.display = 'none';
|
||
|
||
// Open modal
|
||
const modal = new bootstrap.Modal(document.getElementById('sourceDetailsModal'));
|
||
modal.show();
|
||
|
||
// Build URL with filter parameters
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const geoDateFrom = urlParams.get('geo_date_from');
|
||
const geoDateTo = urlParams.get('geo_date_to');
|
||
|
||
let apiUrl = '/api/source/' + sourceId + '/objitems/';
|
||
const params = new URLSearchParams();
|
||
if (geoDateFrom) {
|
||
params.append('geo_date_from', geoDateFrom);
|
||
}
|
||
if (geoDateTo) {
|
||
params.append('geo_date_to', geoDateTo);
|
||
}
|
||
if (params.toString()) {
|
||
apiUrl += '?' + params.toString();
|
||
}
|
||
|
||
// Fetch data from API
|
||
fetch(apiUrl)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
if (response.status === 404) {
|
||
throw new Error('Объект не найден');
|
||
} else {
|
||
throw new Error('Ошибка при загрузке данных');
|
||
}
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
// 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';
|
||
document.getElementById('objitemCount').textContent = data.objitems.length;
|
||
|
||
// Populate table
|
||
const tbody = document.getElementById('objitemTableBody');
|
||
tbody.innerHTML = '';
|
||
|
||
data.objitems.forEach(objitem => {
|
||
const row = document.createElement('tr');
|
||
|
||
// Build transponder cell
|
||
let transponderCell = '-';
|
||
if (objitem.has_transponder) {
|
||
transponderCell = '<a href="#" class="text-success text-decoration-none" ' +
|
||
'onclick="showTransponderModal(' + objitem.transponder_id + '); return false;" ' +
|
||
'title="Показать данные транспондера">' +
|
||
'<i class="bi bi-broadcast"></i> ' + objitem.transponder_info +
|
||
'</a>';
|
||
}
|
||
|
||
// Build LyngSat cell
|
||
let lyngsatCell = '-';
|
||
if (objitem.has_lyngsat) {
|
||
lyngsatCell = '<a href="#" class="text-primary text-decoration-none" ' +
|
||
'onclick="showLyngsatModal(' + objitem.lyngsat_id + '); return false;">' +
|
||
'<i class="bi bi-tv"></i> ТВ' +
|
||
'</a>';
|
||
}
|
||
|
||
// Build Sigma cell
|
||
let sigmaCell = '-';
|
||
if (objitem.has_sigma) {
|
||
sigmaCell = '<a href="#" class="text-info text-decoration-none" ' +
|
||
'onclick="showSigmaParameterModal(' + objitem.parameter_id + '); return false;" ' +
|
||
'title="' + objitem.sigma_info + '">' +
|
||
'<i class="bi bi-graph-up"></i> ' + objitem.sigma_info +
|
||
'</a>';
|
||
}
|
||
|
||
row.innerHTML = '<td class="text-center">' +
|
||
'<input type="checkbox" class="form-check-input modal-item-checkbox" value="' + objitem.id + '">' +
|
||
'</td>' +
|
||
'<td class="text-center">' + objitem.id + '</td>' +
|
||
'<td>' + objitem.name + '</td>' +
|
||
'<td>' + objitem.satellite_name + '</td>' +
|
||
'<td>' + transponderCell + '</td>' +
|
||
'<td>' + objitem.frequency + '</td>' +
|
||
'<td>' + objitem.freq_range + '</td>' +
|
||
'<td>' + objitem.polarization + '</td>' +
|
||
'<td>' + objitem.bod_velocity + '</td>' +
|
||
'<td>' + objitem.modulation + '</td>' +
|
||
'<td>' + objitem.snr + '</td>' +
|
||
'<td>' + objitem.geo_timestamp + '</td>' +
|
||
'<td>' + objitem.geo_location + '</td>' +
|
||
'<td>' + objitem.geo_coords + '</td>' +
|
||
'<td>' + objitem.updated_at + '</td>' +
|
||
'<td>' + objitem.updated_by + '</td>' +
|
||
'<td>' + objitem.created_at + '</td>' +
|
||
'<td>' + objitem.created_by + '</td>' +
|
||
'<td>' + objitem.comment + '</td>' +
|
||
'<td>' + objitem.is_average + '</td>' +
|
||
'<td>' + objitem.standard + '</td>' +
|
||
'<td>' + lyngsatCell + '</td>' +
|
||
'<td>' + sigmaCell + '</td>' +
|
||
'<td>' + objitem.mirrors + '</td>';
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
// Setup modal select-all checkbox
|
||
setupModalSelectAll();
|
||
|
||
// Initialize column visibility
|
||
initModalColumnVisibility();
|
||
} else {
|
||
// Show no data message
|
||
document.getElementById('modalNoData').style.display = 'block';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
// Hide loading spinner
|
||
document.getElementById('modalLoadingSpinner').style.display = 'none';
|
||
|
||
// Show error message
|
||
const errorDiv = document.getElementById('modalErrorMessage');
|
||
errorDiv.textContent = error.message;
|
||
errorDiv.style.display = 'block';
|
||
});
|
||
}
|
||
|
||
// Setup select-all functionality for modal
|
||
function setupModalSelectAll() {
|
||
const modalSelectAll = document.getElementById('modal-select-all');
|
||
const modalItemCheckboxes = document.querySelectorAll('.modal-item-checkbox');
|
||
|
||
if (modalSelectAll && modalItemCheckboxes.length > 0) {
|
||
// Remove old event listeners by cloning
|
||
const newModalSelectAll = modalSelectAll.cloneNode(true);
|
||
modalSelectAll.parentNode.replaceChild(newModalSelectAll, modalSelectAll);
|
||
|
||
newModalSelectAll.addEventListener('change', function() {
|
||
const checkboxes = document.querySelectorAll('.modal-item-checkbox');
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.checked = newModalSelectAll.checked;
|
||
});
|
||
});
|
||
|
||
modalItemCheckboxes.forEach(checkbox => {
|
||
checkbox.addEventListener('change', function() {
|
||
const allCheckboxes = document.querySelectorAll('.modal-item-checkbox');
|
||
const allChecked = Array.from(allCheckboxes).every(cb => cb.checked);
|
||
document.getElementById('modal-select-all').checked = allChecked;
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// Function to toggle modal column visibility
|
||
function toggleModalColumn(checkbox) {
|
||
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
|
||
const modal = document.getElementById('sourceDetailsModal');
|
||
const table = modal.querySelector('.table');
|
||
if (!table) return;
|
||
|
||
const cells = table.querySelectorAll('td:nth-child(' + (columnIndex + 1) + '), th:nth-child(' + (columnIndex + 1) + ')');
|
||
|
||
if (checkbox.checked) {
|
||
cells.forEach(cell => {
|
||
cell.style.display = '';
|
||
});
|
||
} else {
|
||
cells.forEach(cell => {
|
||
cell.style.display = 'none';
|
||
});
|
||
}
|
||
}
|
||
|
||
// Function to toggle all modal columns
|
||
function toggleAllModalColumns(selectAllCheckbox) {
|
||
const columnCheckboxes = document.querySelectorAll('.modal-column-toggle');
|
||
columnCheckboxes.forEach(checkbox => {
|
||
checkbox.checked = selectAllCheckbox.checked;
|
||
toggleModalColumn(checkbox);
|
||
});
|
||
}
|
||
|
||
// Initialize modal column visibility
|
||
function initModalColumnVisibility() {
|
||
// Hide columns by default: Создано (16), Кем(созд) (17), Комментарий (18), Усреднённое (19), Стандарт (20), Sigma (22)
|
||
const columnsToHide = [16, 17, 18, 19, 20, 22];
|
||
columnsToHide.forEach(columnIndex => {
|
||
const checkbox = document.querySelector('.modal-column-toggle[data-column="' + columnIndex + '"]');
|
||
if (checkbox && !checkbox.checked) {
|
||
toggleModalColumn(checkbox);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Function to show LyngSat modal
|
||
function showLyngsatModal(lyngsatId) {
|
||
const modal = new bootstrap.Modal(document.getElementById('lyngsatModal'));
|
||
modal.show();
|
||
|
||
const modalBody = document.getElementById('lyngsatModalBody');
|
||
modalBody.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||
|
||
fetch('/api/lyngsat/' + lyngsatId + '/')
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка загрузки данных');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
let html = '<div class="container-fluid"><div class="row g-3">' +
|
||
'<div class="col-md-6"><div class="card h-100">' +
|
||
'<div class="card-header bg-light"><strong><i class="bi bi-info-circle"></i> Основная информация</strong></div>' +
|
||
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||
'<tr><td class="text-muted" style="width: 40%;">Спутник:</td><td><strong>' + data.satellite + '</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Частота:</td><td><strong>' + data.frequency + ' МГц</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Поляризация:</td><td><span class="badge bg-info">' + data.polarization + '</span></td></tr>' +
|
||
'<tr><td class="text-muted">Канал:</td><td>' + data.channel_info + '</td></tr>' +
|
||
'</tbody></table></div></div></div>' +
|
||
'<div class="col-md-6"><div class="card h-100">' +
|
||
'<div class="card-header bg-light"><strong><i class="bi bi-gear"></i> Технические параметры</strong></div>' +
|
||
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||
'<tr><td class="text-muted" style="width: 40%;">Модуляция:</td><td><span class="badge bg-secondary">' + data.modulation + '</span></td></tr>' +
|
||
'<tr><td class="text-muted">Стандарт:</td><td><span class="badge bg-secondary">' + data.standard + '</span></td></tr>' +
|
||
'<tr><td class="text-muted">Сим. скорость:</td><td><strong>' + data.sym_velocity + ' БОД</strong></td></tr>' +
|
||
'<tr><td class="text-muted">FEC:</td><td>' + data.fec + '</td></tr>' +
|
||
'</tbody></table></div></div></div>' +
|
||
'<div class="col-12"><div class="card">' +
|
||
'<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>' +
|
||
'<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>';
|
||
modalBody.innerHTML = html;
|
||
})
|
||
.catch(error => {
|
||
modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' +
|
||
'<i class="bi bi-exclamation-triangle"></i> ' + error.message + '</div>';
|
||
});
|
||
}
|
||
|
||
// Function to show transponder modal
|
||
function showTransponderModal(transponderId) {
|
||
const modal = new bootstrap.Modal(document.getElementById('transponderModal'));
|
||
modal.show();
|
||
|
||
const modalBody = document.getElementById('transponderModalBody');
|
||
modalBody.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-success" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||
|
||
fetch('/api/transponder/' + transponderId + '/')
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка загрузки данных транспондера');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
let html = '<div class="container-fluid"><div class="row g-3">' +
|
||
'<div class="col-md-6"><div class="card h-100">' +
|
||
'<div class="card-header bg-light"><strong><i class="bi bi-info-circle"></i> Основная информация</strong></div>' +
|
||
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||
'<tr><td class="text-muted" style="width: 40%;">Название:</td><td><strong>' + (data.name || '-') + '</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Спутник:</td><td><strong>' + data.satellite + '</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Зона покрытия:</td><td>' + (data.zone_name || '-') + '</td></tr>' +
|
||
'<tr><td class="text-muted">Поляризация:</td><td><span class="badge bg-info">' + data.polarization + '</span></td></tr>' +
|
||
'</tbody></table></div></div></div>' +
|
||
'<div class="col-md-6"><div class="card h-100">' +
|
||
'<div class="card-header bg-light"><strong><i class="bi bi-broadcast"></i> Частотные параметры</strong></div>' +
|
||
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||
'<tr><td class="text-muted" style="width: 40%;">Downlink:</td><td><strong>' + data.downlink + ' МГц</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Uplink:</td><td><strong>' + (data.uplink || '-') + (data.uplink ? ' МГц' : '') + '</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Полоса:</td><td><strong>' + data.frequency_range + ' МГц</strong></td></tr>' +
|
||
'<tr><td class="text-muted">Перенос:</td><td>' + (data.transfer || '-') + (data.transfer ? ' МГц' : '') + '</td></tr>' +
|
||
'<tr><td class="text-muted">ОСШ:</td><td><strong>' + (data.snr || '-') + (data.snr ? ' дБ' : '') + '</strong></td></tr>' +
|
||
'</tbody></table></div></div></div>' +
|
||
'<div class="col-12"><div class="card">' +
|
||
'<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.created_at || '-') + '</strong></p></div>' +
|
||
'<div class="col-md-6"><p class="mb-2"><span class="text-muted">Создан пользователем:</span><br><strong>' + (data.created_by || '-') + '</strong></p></div>' +
|
||
'</div></div></div></div></div></div>';
|
||
modalBody.innerHTML = html;
|
||
})
|
||
.catch(error => {
|
||
modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' +
|
||
'<i class="bi bi-exclamation-triangle"></i> ' + error.message + '</div>';
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<!-- LyngSat Data Modal -->
|
||
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<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
|
||
</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
||
aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body" id="lyngsatModalBody">
|
||
<div class="text-center py-4">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">Загрузка...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Transponder Data Modal -->
|
||
<div class="modal fade" id="transponderModal" tabindex="-1" aria-labelledby="transponderModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header bg-success text-white">
|
||
<h5 class="modal-title" id="transponderModalLabel">
|
||
<i class="bi bi-broadcast"></i> Данные транспондера
|
||
</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body" id="transponderModalBody">
|
||
<div class="text-center py-4">
|
||
<div class="spinner-border text-success" role="status">
|
||
<span class="visually-hidden">Загрузка...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Include the sigma parameter modal component -->
|
||
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
|
||
|
||
{% endblock %}
|