Добавил транспондеры к ObjItem шаблону
This commit is contained in:
@@ -22,24 +22,25 @@
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=0 column_label="Выбрать" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=1 column_label="Имя" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=2 column_label="Спутник" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=3 column_label="Част, МГц" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=4 column_label="Полоса, МГц" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=5 column_label="Поляризация" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=6 column_label="Сим. V" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=7 column_label="Модул" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=8 column_label="ОСШ" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=9 column_label="Время ГЛ" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=10 column_label="Местоположение" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=11 column_label="Геолокация" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=12 column_label="Обновлено" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=13 column_label="Кем (обновление)" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=14 column_label="Создано" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=15 column_label="Кем (создание)" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=16 column_label="Комментарий" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=17 column_label="Усреднённое" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=18 column_label="Стандарт" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=19 column_label="Тип источника" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=20 column_label="Sigma" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=21 column_label="Зеркала" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=3 column_label="Транспондер" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=4 column_label="Част, МГц" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=5 column_label="Полоса, МГц" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=6 column_label="Поляризация" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=7 column_label="Сим. V" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=8 column_label="Модул" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=9 column_label="ОСШ" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=10 column_label="Время ГЛ" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=11 column_label="Местоположение" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=12 column_label="Геолокация" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=13 column_label="Обновлено" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=14 column_label="Кем (обновление)" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=15 column_label="Создано" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=16 column_label="Кем (создание)" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=17 column_label="Комментарий" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=18 column_label="Усреднённое" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=19 column_label="Стандарт" checked=False %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=20 column_label="Тип источника" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=21 column_label="Sigma" checked=True %}
|
||||
{% include 'mainapp/components/_column_toggle_item.html' with column_index=22 column_label="Зеркала" checked=True %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -32,6 +32,7 @@
|
||||
</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>
|
||||
@@ -41,8 +42,6 @@
|
||||
<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>
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
|
||||
<!-- 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">
|
||||
<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>
|
||||
@@ -82,10 +83,11 @@
|
||||
<i class="bi bi-plus-circle"></i> Добавить к
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Selected Items Counter Button -->
|
||||
<div>
|
||||
<button class="btn btn-outline-info btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#selectedItemsOffcanvas" aria-controls="selectedItemsOffcanvas">
|
||||
<button class="btn btn-outline-info btn-sm" type="button" data-bs-toggle="offcanvas"
|
||||
data-bs-target="#selectedItemsOffcanvas" aria-controls="selectedItemsOffcanvas">
|
||||
<i class="bi bi-list-check"></i> Список
|
||||
<span id="selectedCounter" class="badge bg-info" style="display: none;">0</span>
|
||||
</button>
|
||||
@@ -112,295 +114,277 @@
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
<select name="modulation" class="form-select form-select-sm mb-2" multiple size="6">
|
||||
{% 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>
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- Source Type 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_source_type" id="has_source_type_1"
|
||||
value="1" {% if has_source_type == '1' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_source_type_1">Есть (ТВ)</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_source_type" id="has_source_type_0"
|
||||
value="0" {% if has_source_type == '0' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_source_type_0">Нет</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sigma Filter -->
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Sigma:</label>
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_1"
|
||||
value="1" {% if has_sigma == '1' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_sigma_1">Есть</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_0"
|
||||
value="0" {% if has_sigma == '0' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_sigma_0">Нет</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Filter -->
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Дата ГЛ:</label>
|
||||
<div class="mb-2">
|
||||
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('today')">Сегодня</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('week')">Неделя</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('month')">Месяц</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('year')">Год</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Table -->
|
||||
<div class="col-md">
|
||||
<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>
|
||||
{% include 'mainapp/components/_table_header.html' with label="Имя" field="name" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Спутник" field="satellite" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Част, МГц" field="frequency" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Полоса, МГц" field="freq_range" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Поляризация" field="polarization" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Сим. V" field="bod_velocity" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Модул" field="modulation" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="ОСШ" field="snr" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" 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="Обновлено" field="updated_at" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Кем(обн)" field="" sortable=False %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Создано" field="created_at" 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="Усреднённое" 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="Sigma" field="" sortable=False %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Зеркала" field="" sortable=False %}
|
||||
</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><a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></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_timestamp|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.geo_location}}</td>
|
||||
<td>{{ item.geo_coords }}</td>
|
||||
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.updated_by }}</td>
|
||||
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.obj.created_by }}</td>
|
||||
<td>{{ item.comment }}</td>
|
||||
<td>{{ item.is_average }}</td>
|
||||
<td>{{ item.standard }}</td>
|
||||
<td>
|
||||
{% if item.obj.lyngsat_source %}
|
||||
<a href="#" class="text-primary text-decoration-none" onclick="showLyngsatModal({{ item.obj.lyngsat_source.id }}); return false;">
|
||||
<i class="bi bi-tv"></i> ТВ
|
||||
</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.has_sigma %}
|
||||
<a href="#" class="text-info text-decoration-none" onclick="showSigmaParameterModal({{ item.obj.parameter_obj.id }}); return false;" title="{{ item.sigma_info }}">
|
||||
<i class="bi bi-graph-up"></i> {{ item.sigma_info }}
|
||||
</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.mirrors }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="22" class="text-center py-4">
|
||||
{% if selected_satellite_id %}
|
||||
Нет данных для выбранных фильтров
|
||||
{% else %}
|
||||
Пожалуйста, выберите спутник для отображения данных
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
<select name="modulation" class="form-select form-select-sm mb-2" multiple size="6">
|
||||
{% 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>
|
||||
<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>
|
||||
|
||||
|
||||
<!-- Source Type 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_source_type"
|
||||
id="has_source_type_1" value="1" {% if has_source_type == '1' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_source_type_1">Есть (ТВ)</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_source_type"
|
||||
id="has_source_type_0" value="0" {% if has_source_type == '0' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_source_type_0">Нет</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sigma Filter -->
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Sigma:</label>
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_1" value="1"
|
||||
{% if has_sigma == '1' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_sigma_1">Есть</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_0" value="0"
|
||||
{% if has_sigma == '0' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="has_sigma_0">Нет</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Filter -->
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Дата ГЛ:</label>
|
||||
<div class="mb-2">
|
||||
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('today')">Сегодня</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('week')">Неделя</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('month')">Месяц</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
onclick="setDateRange('year')">Год</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- 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">
|
||||
<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>
|
||||
{% include 'mainapp/components/_table_header.html' with label="Имя" field="name" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Спутник" field="satellite" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Транспондер" field="" sortable=False %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Част, МГц" field="frequency" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Полоса, МГц" field="freq_range" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Поляризация" field="polarization" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Сим. V" field="bod_velocity" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Модул" field="modulation" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="ОСШ" field="snr" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" 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="Обновлено" field="updated_at" sort=sort %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Кем(обн)" field="" sortable=False %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Создано" field="created_at" 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="Усреднённое" 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="Sigma" field="" sortable=False %}
|
||||
{% include 'mainapp/components/_table_header.html' with label="Зеркала" field="" sortable=False %}
|
||||
</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>
|
||||
<a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
|
||||
<td>{{ item.satellite_name }}</td>
|
||||
<td>
|
||||
{% if item.obj.transponder %}
|
||||
<a href="#" class="text-success text-decoration-none"
|
||||
onclick="showTransponderModal({{ item.obj.transponder.id }}); return false;"
|
||||
title="Показать данные транспондера">
|
||||
<i class="bi bi-broadcast"></i> {{ item.obj.transponder.downlink }}:{{ item.obj.transponder.frequency_range }}
|
||||
</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</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_timestamp|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.geo_location}}</td>
|
||||
<td>{{ item.geo_coords }}</td>
|
||||
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.updated_by }}</td>
|
||||
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.obj.created_by }}</td>
|
||||
<td>{{ item.comment }}</td>
|
||||
<td>{{ item.is_average }}</td>
|
||||
<td>{{ item.standard }}</td>
|
||||
<td>
|
||||
{% if item.obj.lyngsat_source %}
|
||||
<a href="#" class="text-primary text-decoration-none"
|
||||
onclick="showLyngsatModal({{ item.obj.lyngsat_source.id }}); return false;">
|
||||
<i class="bi bi-tv"></i> ТВ
|
||||
</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.has_sigma %}
|
||||
<a href="#" class="text-info text-decoration-none"
|
||||
onclick="showSigmaParameterModal({{ item.obj.parameter_obj.id }}); return false;"
|
||||
title="{{ item.sigma_info }}">
|
||||
<i class="bi bi-graph-up"></i> {{ item.sigma_info }}
|
||||
</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.mirrors }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="22" class="text-center py-4">
|
||||
{% if selected_satellite_id %}
|
||||
Нет данных для выбранных фильтров
|
||||
{% else %}
|
||||
Пожалуйста, выберите спутник для отображения данных
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
@@ -720,8 +704,8 @@
|
||||
|
||||
// Initialize column visibility - hide creation columns by default
|
||||
function initColumnVisibility() {
|
||||
const creationDateCheckbox = document.querySelector('input[data-column="14"]');
|
||||
const creationUserCheckbox = document.querySelector('input[data-column="15"]');
|
||||
const creationDateCheckbox = document.querySelector('input[data-column="15"]');
|
||||
const creationUserCheckbox = document.querySelector('input[data-column="16"]');
|
||||
if (creationDateCheckbox) {
|
||||
creationDateCheckbox.checked = false;
|
||||
toggleColumn(creationDateCheckbox);
|
||||
@@ -731,22 +715,22 @@
|
||||
creationUserCheckbox.checked = false;
|
||||
toggleColumn(creationUserCheckbox);
|
||||
}
|
||||
|
||||
|
||||
// Hide comment, is_average, and standard columns by default
|
||||
const commentCheckbox = document.querySelector('input[data-column="16"]');
|
||||
const isAverageCheckbox = document.querySelector('input[data-column="17"]');
|
||||
const standardCheckbox = document.querySelector('input[data-column="18"]');
|
||||
|
||||
const commentCheckbox = document.querySelector('input[data-column="17"]');
|
||||
const isAverageCheckbox = document.querySelector('input[data-column="18"]');
|
||||
const standardCheckbox = document.querySelector('input[data-column="19"]');
|
||||
|
||||
if (commentCheckbox) {
|
||||
commentCheckbox.checked = false;
|
||||
toggleColumn(commentCheckbox);
|
||||
}
|
||||
|
||||
|
||||
if (isAverageCheckbox) {
|
||||
isAverageCheckbox.checked = false;
|
||||
toggleColumn(isAverageCheckbox);
|
||||
}
|
||||
|
||||
|
||||
if (standardCheckbox) {
|
||||
standardCheckbox.checked = false;
|
||||
toggleColumn(standardCheckbox);
|
||||
@@ -785,7 +769,7 @@
|
||||
// Count checkbox filters
|
||||
const hasKupsatCheckboxes = document.querySelectorAll('input[name="has_kupsat"]:checked');
|
||||
const hasValidCheckboxes = document.querySelectorAll('input[name="has_valid"]:checked');
|
||||
|
||||
|
||||
if (hasKupsatCheckboxes.length > 0) {
|
||||
filterCount++;
|
||||
}
|
||||
@@ -853,7 +837,7 @@
|
||||
}
|
||||
|
||||
// Function to save selected items to localStorage
|
||||
window.saveSelectedItemsToStorage = function() {
|
||||
window.saveSelectedItemsToStorage = function () {
|
||||
try {
|
||||
localStorage.setItem('selectedItems', JSON.stringify(window.selectedItems));
|
||||
} catch (e) {
|
||||
@@ -862,7 +846,7 @@
|
||||
}
|
||||
|
||||
// Function to update the selected items counter
|
||||
window.updateSelectedCounter = function() {
|
||||
window.updateSelectedCounter = function () {
|
||||
const counterElement = document.getElementById('selectedCounter');
|
||||
if (window.selectedItems && window.selectedItems.length > 0) {
|
||||
counterElement.textContent = window.selectedItems.length;
|
||||
@@ -870,7 +854,7 @@
|
||||
} else {
|
||||
counterElement.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
// Also update the counter in the offcanvas
|
||||
const offcanvasCounter = document.querySelector('#selectedItemsOffcanvas .offcanvas-header .badge');
|
||||
if (offcanvasCounter && window.selectedItems && window.selectedItems.length > 0) {
|
||||
@@ -885,9 +869,9 @@
|
||||
updateSelectedCounter();
|
||||
|
||||
// Function to add selected items to the list
|
||||
window.addSelectedToList = function() {
|
||||
window.addSelectedToList = function () {
|
||||
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
|
||||
|
||||
|
||||
if (checkedCheckboxes.length === 0) {
|
||||
alert('Пожалуйста, выберите хотя бы один элемент для добавления в список');
|
||||
return;
|
||||
@@ -897,31 +881,30 @@
|
||||
checkedCheckboxes.forEach(checkbox => {
|
||||
const row = checkbox.closest('tr');
|
||||
const itemId = checkbox.value;
|
||||
|
||||
|
||||
const itemExists = window.selectedItems.some(item => item.id === itemId);
|
||||
if (!itemExists) {
|
||||
const rowData = {
|
||||
id: itemId,
|
||||
name: row.cells[1].textContent,
|
||||
satellite: row.cells[2].textContent,
|
||||
frequency: row.cells[3].textContent,
|
||||
freq_range: row.cells[4].textContent,
|
||||
polarization: row.cells[5].textContent,
|
||||
bod_velocity: row.cells[6].textContent,
|
||||
modulation: row.cells[7].textContent,
|
||||
snr: row.cells[8].textContent,
|
||||
geo_timestamp: row.cells[9].textContent,
|
||||
geo_location: row.cells[10].textContent,
|
||||
geo_coords: row.cells[11].textContent,
|
||||
kupsat_coords: row.cells[12].textContent,
|
||||
valid_coords: row.cells[13].textContent,
|
||||
updated_at: row.cells[12].textContent,
|
||||
updated_by: row.cells[13].textContent,
|
||||
created_at: row.cells[14].textContent,
|
||||
created_by: row.cells[15].textContent,
|
||||
mirrors: row.cells[21].textContent
|
||||
transponder: row.cells[3].textContent,
|
||||
frequency: row.cells[4].textContent,
|
||||
freq_range: row.cells[5].textContent,
|
||||
polarization: row.cells[6].textContent,
|
||||
bod_velocity: row.cells[7].textContent,
|
||||
modulation: row.cells[8].textContent,
|
||||
snr: row.cells[9].textContent,
|
||||
geo_timestamp: row.cells[10].textContent,
|
||||
geo_location: row.cells[11].textContent,
|
||||
geo_coords: row.cells[12].textContent,
|
||||
updated_at: row.cells[13].textContent,
|
||||
updated_by: row.cells[14].textContent,
|
||||
created_at: row.cells[15].textContent,
|
||||
created_by: row.cells[16].textContent,
|
||||
mirrors: row.cells[22].textContent
|
||||
};
|
||||
|
||||
|
||||
window.selectedItems.push(rowData);
|
||||
}
|
||||
});
|
||||
@@ -966,6 +949,7 @@
|
||||
</td>
|
||||
<td>${item.name}</td>
|
||||
<td>${item.satellite}</td>
|
||||
<td>${item.transponder}</td>
|
||||
<td>${item.frequency}</td>
|
||||
<td>${item.freq_range}</td>
|
||||
<td>${item.polarization}</td>
|
||||
@@ -975,8 +959,6 @@
|
||||
<td>${item.geo_timestamp}</td>
|
||||
<td>${item.geo_location}</td>
|
||||
<td>${item.geo_coords}</td>
|
||||
<td>${item.kupsat_coords}</td>
|
||||
<td>${item.valid_coords}</td>
|
||||
<td>${item.updated_at}</td>
|
||||
<td>${item.updated_by}</td>
|
||||
<td>${item.created_at}</td>
|
||||
@@ -997,10 +979,10 @@
|
||||
|
||||
// Get IDs of items to remove
|
||||
const idsToRemove = Array.from(checkboxes).map(checkbox => checkbox.value);
|
||||
|
||||
|
||||
// Remove items from the selectedItems array
|
||||
window.selectedItems = window.selectedItems.filter(item => !idsToRemove.includes(item.id));
|
||||
|
||||
|
||||
// Save selected items to localStorage
|
||||
saveSelectedItemsToStorage();
|
||||
|
||||
@@ -1018,7 +1000,7 @@
|
||||
alert('Пожалуйста, выберите хотя бы один элемент для отправки');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
alert(`Отправка ${selectedCount} элементов... (функция в разработке)`);
|
||||
// Placeholder for actual send functionality
|
||||
}
|
||||
@@ -1032,10 +1014,10 @@
|
||||
}
|
||||
|
||||
// Update the selected items table when the offcanvas is shown
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const offcanvasElement = document.getElementById('selectedItemsOffcanvas');
|
||||
if (offcanvasElement) {
|
||||
offcanvasElement.addEventListener('show.bs.offcanvas', function() {
|
||||
offcanvasElement.addEventListener('show.bs.offcanvas', function () {
|
||||
populateSelectedItemsTable();
|
||||
});
|
||||
}
|
||||
@@ -1056,7 +1038,8 @@
|
||||
<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>
|
||||
<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">
|
||||
@@ -1073,32 +1056,32 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showLyngsatModal(lyngsatId) {
|
||||
// Показываем модальное окно
|
||||
const modal = new bootstrap.Modal(document.getElementById('lyngsatModal'));
|
||||
modal.show();
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
const modalBody = document.getElementById('lyngsatModalBody');
|
||||
modalBody.innerHTML = `
|
||||
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 => {
|
||||
// Формируем HTML с данными
|
||||
let html = `
|
||||
|
||||
// Загружаем данные
|
||||
fetch(`/api/lyngsat/${lyngsatId}/`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Ошибка загрузки данных');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Формируем HTML с данными
|
||||
let html = `
|
||||
<div class="container-fluid">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
@@ -1191,16 +1174,139 @@ function showLyngsatModal(lyngsatId) {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modalBody.innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
modalBody.innerHTML = `
|
||||
|
||||
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>
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,10 +1,74 @@
|
||||
{% extends "mapsapp/map2d_base.html" %}
|
||||
{% extends "mainapp/base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Карта выбранных объектов{% endblock title %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Leaflet CSS -->
|
||||
<link href="{% static 'leaflet/leaflet.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-measure/leaflet-measure.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-tree/L.Control.Layers.Tree.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
#map {
|
||||
position: fixed;
|
||||
top: 56px; /* Высота navbar */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="map"></div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Leaflet JavaScript -->
|
||||
<script src="{% static 'leaflet/leaflet.js' %}"></script>
|
||||
<script src="{% static 'leaflet-measure/leaflet-measure.ru.js' %}"></script>
|
||||
<script src="{% static 'leaflet-tree/L.Control.Layers.Tree.js' %}"></script>
|
||||
|
||||
<script>
|
||||
// Цвета для стандартных маркеров (из leaflet-color-markers)
|
||||
// Инициализация карты
|
||||
let map = L.map('map').setView([55.75, 37.62], 5);
|
||||
L.control.scale({
|
||||
imperial: false,
|
||||
metric: true
|
||||
}).addTo(map);
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
const street = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
street.addTo(map);
|
||||
|
||||
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||
attribution: 'Tiles © Esri'
|
||||
});
|
||||
|
||||
const street_local = L.tileLayer('http://127.0.0.1:8080/styles/basic-preview/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: 'Local Tiles'
|
||||
});
|
||||
|
||||
const baseLayers = {
|
||||
"Улицы": street,
|
||||
"Спутник": satellite,
|
||||
"Локально": street_local
|
||||
};
|
||||
|
||||
L.control.layers(baseLayers).addTo(map);
|
||||
map.setMaxZoom(18);
|
||||
map.setMinZoom(0);
|
||||
L.control.measure({ primaryLengthUnit: 'kilometers' }).addTo(map);
|
||||
|
||||
// Цвета для маркеров
|
||||
var markerColors = ['red', 'green', 'orange', 'violet', 'grey', 'black', 'blue'];
|
||||
var getColorIcon = function(color) {
|
||||
return L.icon({
|
||||
@@ -19,47 +83,52 @@
|
||||
|
||||
var overlays = [];
|
||||
|
||||
// Создаём слои для каждого объекта
|
||||
{% for group in groups %}
|
||||
var groupIndex = {{ forloop.counter0 }};
|
||||
var groupName = '{{ group.name|escapejs }}';
|
||||
var colorName = markerColors[groupIndex % markerColors.length];
|
||||
var groupIcon = getColorIcon(colorName);
|
||||
|
||||
var groupLayer = L.layerGroup();
|
||||
|
||||
var subgroup = [];
|
||||
{% for point_data in group.points %}
|
||||
var pointName = "{{ group.name|escapejs }}";
|
||||
|
||||
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
|
||||
icon: groupIcon
|
||||
}).bindPopup(pointName + '<br>' + "{{ point_data.frequency|escapejs }}");
|
||||
|
||||
groupLayer.addLayer(marker);
|
||||
|
||||
subgroup.push({
|
||||
label: "{{ forloop.counter }} - {{ point_data.frequency }}",
|
||||
label: "{{ forloop.counter }} - {{ point_data.frequency|escapejs }}",
|
||||
layer: marker
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
overlays.push({
|
||||
label: '{{ group.name|escapejs }}',
|
||||
label: groupName,
|
||||
selectAllCheckbox: true,
|
||||
children: subgroup,
|
||||
layer: groupLayer
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
// Create the layer control with a custom container that includes a select all checkbox
|
||||
var layerControl = L.control.layers.tree(baseLayers, overlays, {
|
||||
// Корневая группа
|
||||
const rootGroup = {
|
||||
label: "Все точки",
|
||||
selectAllCheckbox: true,
|
||||
children: overlays,
|
||||
layer: L.layerGroup()
|
||||
};
|
||||
|
||||
// Создаём tree control
|
||||
const layerControl = L.control.layers.tree(baseLayers, [rootGroup], {
|
||||
collapsed: false,
|
||||
autoZIndex: true
|
||||
});
|
||||
|
||||
// Add the layer control to the map
|
||||
layerControl.addTo(map);
|
||||
|
||||
// Calculate map bounds to fit all markers
|
||||
// Подгоняем карту под все маркеры
|
||||
{% if groups %}
|
||||
var groupBounds = L.featureGroup([]);
|
||||
{% for group in groups %}
|
||||
@@ -67,40 +136,7 @@
|
||||
groupBounds.addLayer(L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}]));
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
map.fitBounds(groupBounds.getBounds().pad(0.1)); // Add some padding
|
||||
{% else %}
|
||||
map.setView([55.75, 37.62], 5); // Default view if no markers
|
||||
map.fitBounds(groupBounds.getBounds().pad(0.1));
|
||||
{% endif %}
|
||||
|
||||
// Add a "Select All" checkbox functionality for all overlays
|
||||
setTimeout(function() {
|
||||
// Create a custom "select all" checkbox
|
||||
var selectAllContainer = document.createElement('div');
|
||||
selectAllContainer.className = 'leaflet-control-layers-select-all';
|
||||
selectAllContainer.style.padding = '5px';
|
||||
selectAllContainer.style.borderBottom = '1px solid #ccc';
|
||||
selectAllContainer.style.marginBottom = '5px';
|
||||
selectAllContainer.innerHTML = '<label><input type="checkbox" id="select-all-overlays" checked> Показать все точки</label>';
|
||||
|
||||
// Insert the checkbox at the top of the layer control
|
||||
var layerControlContainer = document.querySelector('.leaflet-control-layers-list');
|
||||
if (layerControlContainer) {
|
||||
layerControlContainer.insertBefore(selectAllContainer, layerControlContainer.firstChild);
|
||||
}
|
||||
|
||||
// Add event listener to the "select all" checkbox
|
||||
document.getElementById('select-all-overlays').addEventListener('change', function() {
|
||||
var isChecked = this.checked;
|
||||
|
||||
// Iterate through all overlays and toggle visibility
|
||||
for (var i = 0; i < overlays.length; i++) {
|
||||
if (isChecked) {
|
||||
map.addLayer(overlays[i].layer);
|
||||
} else {
|
||||
map.removeLayer(overlays[i].layer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 500); // Slight delay to ensure the tree control has been rendered
|
||||
</script>
|
||||
{% endblock extra_js %}
|
||||
63
dbapp/mainapp/templates/mainapp/source_confirm_delete.html
Normal file
63
dbapp/mainapp/templates/mainapp/source_confirm_delete.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Удалить источник #{{ object.id }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h4 class="mb-0">Подтверждение удаления</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
<strong>Внимание!</strong> Вы собираетесь удалить источник.
|
||||
</div>
|
||||
|
||||
<h5>Информация об источнике:</h5>
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">
|
||||
<strong>ID:</strong> {{ object.id }}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Дата создания:</strong>
|
||||
{% if object.created_at %}{{ object.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Создан пользователем:</strong>
|
||||
{% if object.created_by %}{{ object.created_by }}{% else %}-{% endif %}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Привязанных объектов:</strong>
|
||||
<span class="badge bg-primary">{{ objitems_count }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if objitems_count > 0 %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi bi-exclamation-circle-fill"></i>
|
||||
<strong>Важно!</strong> При удалении источника будут также удалены все {{ objitems_count }} привязанных объектов!
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="text-muted">Это действие нельзя отменить. Вы уверены, что хотите продолжить?</p>
|
||||
|
||||
<form method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash"></i> Да, удалить
|
||||
</button>
|
||||
<a href="{% url 'mainapp:source_update' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
|
||||
class="btn btn-secondary">
|
||||
<i class="bi bi-x-circle"></i> Отмена
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
652
dbapp/mainapp/templates/mainapp/source_form.html
Normal file
652
dbapp/mainapp/templates/mainapp/source_form.html
Normal file
@@ -0,0 +1,652 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
{% load static %}
|
||||
{% load static leaflet_tags %}
|
||||
{% load l10n %}
|
||||
|
||||
{% block title %}Редактировать источник #{{ object.id }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.form-section {
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.form-section-header {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.readonly-field {
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.coord-group {
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.coord-group-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.map-control-btn {
|
||||
padding: 0.375rem 0.75rem;
|
||||
border: 1px solid #ced4da;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map-control-btn.active {
|
||||
background-color: #e9ecef;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
.map-control-btn.edit {
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeeba;
|
||||
}
|
||||
|
||||
.map-control-btn.save {
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
.map-control-btn.cancel {
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon {
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
.objitems-table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.objitems-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.objitems-table td {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.objitems-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.objitem-link {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.objitem-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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>
|
||||
<div>
|
||||
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
|
||||
<button type="submit" form="source-form" class="btn btn-primary btn-action">Сохранить</button>
|
||||
<a href="{% url 'mainapp:source_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
|
||||
class="btn btn-danger btn-action">Удалить</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'mainapp:home' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
|
||||
class="btn btn-secondary btn-action">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" id="source-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Основная информация -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<h4>Основная информация</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">ID источника:</label>
|
||||
<div class="readonly-field">{{ object.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Дата создания:</label>
|
||||
<div class="readonly-field">
|
||||
{% if object.created_at %}{{ object.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Создан пользователем:</label>
|
||||
<div class="readonly-field">
|
||||
{% if object.created_by %}{{ object.created_by }}{% else %}-{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Дата последнего изменения:</label>
|
||||
<div class="readonly-field">
|
||||
{% if object.updated_at %}{{ object.updated_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Изменен пользователем:</label>
|
||||
<div class="readonly-field">
|
||||
{% if object.updated_by %}{{ object.updated_by }}{% else %}-{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блок с картой -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<h4>Карта</h4>
|
||||
</div>
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Координаты -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<h4>Координаты</h4>
|
||||
</div>
|
||||
|
||||
<!-- Координаты ГЛ -->
|
||||
<div class="coord-group">
|
||||
<div class="coord-group-header">Координаты ГЛ (усреднённые)</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_average_latitude" class="form-label">Широта:</label>
|
||||
{{ form.average_latitude }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_average_longitude" class="form-label">Долгота:</label>
|
||||
{{ form.average_longitude }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Координаты Кубсата -->
|
||||
<div class="coord-group">
|
||||
<div class="coord-group-header">Координаты Кубсата</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_kupsat_latitude" class="form-label">Широта:</label>
|
||||
{{ form.kupsat_latitude }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_kupsat_longitude" class="form-label">Долгота:</label>
|
||||
{{ form.kupsat_longitude }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Координаты оперативников -->
|
||||
<div class="coord-group">
|
||||
<div class="coord-group-header">Координаты оперативников</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_valid_latitude" class="form-label">Широта:</label>
|
||||
{{ form.valid_latitude }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_valid_longitude" class="form-label">Долгота:</label>
|
||||
{{ form.valid_longitude }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Координаты справочные -->
|
||||
<div class="coord-group">
|
||||
<div class="coord-group-header">Координаты справочные</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_reference_latitude" class="form-label">Широта:</label>
|
||||
{{ form.reference_latitude }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="id_reference_longitude" class="form-label">Долгота:</label>
|
||||
{{ form.reference_longitude }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Привязанные объекты -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<h4>Привязанные объекты ({{ objitems.count }})</h4>
|
||||
</div>
|
||||
|
||||
{% if objitems %}
|
||||
<div class="table-responsive">
|
||||
<table class="objitems-table table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Спутник</th>
|
||||
<th>Частота, МГц</th>
|
||||
<th>Полоса, МГц</th>
|
||||
<th>Поляризация</th>
|
||||
<th>Модуляция</th>
|
||||
<th>Координаты</th>
|
||||
<th>Создан</th>
|
||||
<th>Обновлен</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in objitems %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'mainapp:objitem_update' item.id %}" class="objitem-link">
|
||||
{{ item.id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ item.name|default:"-" }}</td>
|
||||
<td>
|
||||
{% if item.parameter_obj and item.parameter_obj.id_satellite %}
|
||||
{{ item.parameter_obj.id_satellite.name }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.parameter_obj %}
|
||||
{{ item.parameter_obj.frequency|default:"-" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.parameter_obj %}
|
||||
{{ item.parameter_obj.freq_range|default:"-" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.parameter_obj and item.parameter_obj.polarization %}
|
||||
{{ item.parameter_obj.polarization.name }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.parameter_obj and item.parameter_obj.modulation %}
|
||||
{{ item.parameter_obj.modulation.name }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.geo_obj and item.geo_obj.coords %}
|
||||
{{ item.geo_obj.coords.y|floatformat:6 }}, {{ item.geo_obj.coords.x|floatformat:6 }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.created_at|date:"d.m.Y H:i" }}</td>
|
||||
<td>{{ item.updated_at|date:"d.m.Y H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">Нет привязанных объектов</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<!-- Подключаем Leaflet -->
|
||||
{% leaflet_js %}
|
||||
{% leaflet_css %}
|
||||
<script src="{% static 'leaflet-markers/js/leaflet-color-markers.js' %}"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Инициализация карты
|
||||
const map = L.map('map').setView([55.75, 37.62], 5);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
|
||||
// Функция для создания иконок маркеров разных цветов
|
||||
function createMarkerIcon(color) {
|
||||
return L.icon({
|
||||
iconUrl: `{% static "leaflet-markers/img/marker-icon-" %}${color}.png`,
|
||||
shadowUrl: `{% static 'leaflet-markers/img/marker-shadow.png' %}`,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
}
|
||||
|
||||
const editableLayerGroup = new L.FeatureGroup();
|
||||
map.addLayer(editableLayerGroup);
|
||||
|
||||
// Создаем 4 маркера для разных типов координат
|
||||
const markers = {
|
||||
average: {
|
||||
marker: L.marker([55.75, 37.62], {
|
||||
draggable: false,
|
||||
icon: createMarkerIcon('blue'),
|
||||
title: 'Координаты ГЛ'
|
||||
}).addTo(editableLayerGroup),
|
||||
latField: 'id_average_latitude',
|
||||
lngField: 'id_average_longitude',
|
||||
label: 'Координаты ГЛ'
|
||||
},
|
||||
kupsat: {
|
||||
marker: L.marker([55.75, 37.62], {
|
||||
draggable: false,
|
||||
icon: createMarkerIcon('green'),
|
||||
title: 'Координаты Кубсата'
|
||||
}).addTo(editableLayerGroup),
|
||||
latField: 'id_kupsat_latitude',
|
||||
lngField: 'id_kupsat_longitude',
|
||||
label: 'Координаты Кубсата'
|
||||
},
|
||||
valid: {
|
||||
marker: L.marker([55.75, 37.62], {
|
||||
draggable: false,
|
||||
icon: createMarkerIcon('red'),
|
||||
title: 'Координаты оперативников'
|
||||
}).addTo(editableLayerGroup),
|
||||
latField: 'id_valid_latitude',
|
||||
lngField: 'id_valid_longitude',
|
||||
label: 'Координаты оперативников'
|
||||
},
|
||||
reference: {
|
||||
marker: L.marker([55.75, 37.62], {
|
||||
draggable: false,
|
||||
icon: createMarkerIcon('yellow'),
|
||||
title: 'Координаты справочные'
|
||||
}).addTo(editableLayerGroup),
|
||||
latField: 'id_reference_latitude',
|
||||
lngField: 'id_reference_longitude',
|
||||
label: 'Координаты справочные'
|
||||
}
|
||||
};
|
||||
|
||||
// Привязываем попапы к маркерам
|
||||
Object.values(markers).forEach(m => {
|
||||
m.marker.bindPopup(m.label);
|
||||
});
|
||||
|
||||
// Синхронизация при перетаскивании
|
||||
Object.entries(markers).forEach(([key, m]) => {
|
||||
m.marker.on('dragend', function (event) {
|
||||
const latLng = event.target.getLatLng();
|
||||
document.getElementById(m.latField).value = latLng.lat.toFixed(6);
|
||||
document.getElementById(m.lngField).value = latLng.lng.toFixed(6);
|
||||
});
|
||||
|
||||
// Методы для управления
|
||||
m.marker.enableEditing = function () {
|
||||
this.dragging.enable();
|
||||
this.openPopup();
|
||||
};
|
||||
|
||||
m.marker.disableEditing = function () {
|
||||
this.dragging.disable();
|
||||
this.closePopup();
|
||||
};
|
||||
});
|
||||
|
||||
// Устанавливаем начальные координаты из полей формы
|
||||
function initMarkersFromForm() {
|
||||
let hasValidCoords = false;
|
||||
let centerLat = 55.75;
|
||||
let centerLng = 37.62;
|
||||
|
||||
Object.entries(markers).forEach(([key, m]) => {
|
||||
const lat = parseFloat(document.getElementById(m.latField).value);
|
||||
const lng = parseFloat(document.getElementById(m.lngField).value);
|
||||
|
||||
if (!isNaN(lat) && !isNaN(lng)) {
|
||||
m.marker.setLatLng([lat, lng]);
|
||||
if (!hasValidCoords) {
|
||||
centerLat = lat;
|
||||
centerLng = lng;
|
||||
hasValidCoords = true;
|
||||
}
|
||||
} else {
|
||||
// Скрываем маркер если нет координат
|
||||
m.marker.setOpacity(0);
|
||||
}
|
||||
});
|
||||
|
||||
// Центрируем карту
|
||||
map.setView([centerLat, centerLng], hasValidCoords ? 10 : 5);
|
||||
}
|
||||
|
||||
// Настройка формы для синхронизации с маркерами
|
||||
function setupFormChange(latFieldId, lngFieldId, marker) {
|
||||
const latField = document.getElementById(latFieldId);
|
||||
const lngField = document.getElementById(lngFieldId);
|
||||
|
||||
[latField, lngField].forEach(field => {
|
||||
field.addEventListener('change', function () {
|
||||
const lat = parseFloat(latField.value);
|
||||
const lng = parseFloat(lngField.value);
|
||||
|
||||
if (!isNaN(lat) && !isNaN(lng)) {
|
||||
marker.setLatLng([lat, lng]);
|
||||
marker.setOpacity(1);
|
||||
map.setView(marker.getLatLng(), 10);
|
||||
} else {
|
||||
marker.setOpacity(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
initMarkersFromForm();
|
||||
Object.values(markers).forEach(m => {
|
||||
setupFormChange(m.latField, m.lngField, m.marker);
|
||||
});
|
||||
|
||||
// --- УПРАВЛЕНИЕ РЕДАКТИРОВАНИЕМ ---
|
||||
const editControlsDiv = L.DomUtil.create('div', 'map-controls');
|
||||
editControlsDiv.style.position = 'absolute';
|
||||
editControlsDiv.style.top = '10px';
|
||||
editControlsDiv.style.right = '10px';
|
||||
editControlsDiv.style.zIndex = '1000';
|
||||
editControlsDiv.style.background = 'white';
|
||||
editControlsDiv.style.padding = '10px';
|
||||
editControlsDiv.style.borderRadius = '4px';
|
||||
editControlsDiv.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
|
||||
editControlsDiv.innerHTML = `
|
||||
<div class="map-controls">
|
||||
<button type="button" id="edit-btn" class="map-control-btn edit">Редактировать</button>
|
||||
<button type="button" id="save-btn" class="map-control-btn save" disabled>Сохранить</button>
|
||||
<button type="button" id="cancel-btn" class="map-control-btn cancel" disabled>Отмена</button>
|
||||
</div>
|
||||
`;
|
||||
map.getContainer().appendChild(editControlsDiv);
|
||||
|
||||
let isEditing = false;
|
||||
const initialPositions = {};
|
||||
|
||||
// Сохраняем начальные координаты для отмены
|
||||
Object.entries(markers).forEach(([key, m]) => {
|
||||
initialPositions[key] = m.marker.getLatLng();
|
||||
});
|
||||
|
||||
// Включение редактирования
|
||||
document.getElementById('edit-btn').addEventListener('click', function () {
|
||||
if (isEditing) return;
|
||||
|
||||
isEditing = true;
|
||||
document.getElementById('edit-btn').classList.add('active');
|
||||
document.getElementById('save-btn').disabled = false;
|
||||
document.getElementById('cancel-btn').disabled = false;
|
||||
|
||||
// Включаем drag для всех маркеров
|
||||
Object.values(markers).forEach(m => {
|
||||
if (m.marker.options.opacity !== 0) {
|
||||
m.marker.enableEditing();
|
||||
}
|
||||
});
|
||||
|
||||
// Показываем подсказку
|
||||
L.popup()
|
||||
.setLatLng(map.getCenter())
|
||||
.setContent('Перетаскивайте маркеры. Нажмите "Сохранить" или "Отмена".')
|
||||
.openOn(map);
|
||||
});
|
||||
|
||||
// Сохранение изменений
|
||||
document.getElementById('save-btn').addEventListener('click', function () {
|
||||
if (!isEditing) return;
|
||||
|
||||
isEditing = false;
|
||||
document.getElementById('edit-btn').classList.remove('active');
|
||||
document.getElementById('save-btn').disabled = true;
|
||||
document.getElementById('cancel-btn').disabled = true;
|
||||
|
||||
// Отключаем редактирование
|
||||
Object.values(markers).forEach(m => {
|
||||
m.marker.disableEditing();
|
||||
});
|
||||
|
||||
// Обновляем начальные позиции
|
||||
Object.entries(markers).forEach(([key, m]) => {
|
||||
initialPositions[key] = m.marker.getLatLng();
|
||||
});
|
||||
|
||||
map.closePopup();
|
||||
});
|
||||
|
||||
// Отмена изменений
|
||||
document.getElementById('cancel-btn').addEventListener('click', function () {
|
||||
if (!isEditing) return;
|
||||
|
||||
isEditing = false;
|
||||
document.getElementById('edit-btn').classList.remove('active');
|
||||
document.getElementById('save-btn').disabled = true;
|
||||
document.getElementById('cancel-btn').disabled = true;
|
||||
|
||||
// Возвращаем маркеры на исходные позиции
|
||||
Object.entries(markers).forEach(([key, m]) => {
|
||||
m.marker.setLatLng(initialPositions[key]);
|
||||
m.marker.disableEditing();
|
||||
|
||||
// Синхронизируем форму с исходными значениями
|
||||
document.getElementById(m.latField).value = initialPositions[key].lat.toFixed(6);
|
||||
document.getElementById(m.lngField).value = initialPositions[key].lng.toFixed(6);
|
||||
});
|
||||
|
||||
map.closePopup();
|
||||
});
|
||||
|
||||
// Легенда
|
||||
const legend = L.control({ position: 'bottomright' });
|
||||
|
||||
legend.onAdd = function () {
|
||||
const div = L.DomUtil.create('div', 'info legend');
|
||||
div.style.fontSize = '14px';
|
||||
div.style.backgroundColor = 'white';
|
||||
div.style.padding = '10px';
|
||||
div.style.borderRadius = '4px';
|
||||
div.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
|
||||
div.innerHTML = `
|
||||
<h5>Легенда</h5>
|
||||
<div><span style="color: blue; font-weight: bold;">•</span> Координаты ГЛ</div>
|
||||
<div><span style="color: green; font-weight: bold;">•</span> Координаты Кубсата</div>
|
||||
<div><span style="color: red; font-weight: bold;">•</span> Координаты оперативников</div>
|
||||
<div><span style="color: gold; font-weight: bold;">•</span> Координаты справочные</div>
|
||||
`;
|
||||
return div;
|
||||
};
|
||||
|
||||
legend.addTo(map);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -12,6 +12,16 @@
|
||||
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 %}
|
||||
|
||||
@@ -55,11 +65,20 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="d-flex gap-2">
|
||||
<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>
|
||||
|
||||
@@ -185,6 +204,9 @@
|
||||
<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" class="text-center" style="min-width: 60px;">
|
||||
<a href="javascript:void(0)" onclick="updateSort('id')" class="text-white text-decoration-none">
|
||||
ID
|
||||
@@ -229,12 +251,16 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="text-center" style="min-width: 100px;">Детали</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.coords_average }}</td>
|
||||
<td>{{ source.coords_kupsat }}</td>
|
||||
@@ -244,15 +270,44 @@
|
||||
<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">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="showSourceDetails({{ source.id }})">
|
||||
<i class="bi bi-eye"></i> Показать
|
||||
</button>
|
||||
<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 %}
|
||||
|
||||
<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="9" class="text-center text-muted">Нет данных для отображения</td>
|
||||
<td colspan="10" class="text-center text-muted">Нет данных для отображения</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -285,6 +340,10 @@
|
||||
<table class="table table-striped table-hover table-sm">
|
||||
<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>Имя</th>
|
||||
<th>Спутник</th>
|
||||
<th>Частота, МГц</th>
|
||||
@@ -319,6 +378,55 @@
|
||||
|
||||
{% 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
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
function performSearch() {
|
||||
const searchValue = document.getElementById('toolbar-search').value.trim();
|
||||
@@ -377,6 +485,103 @@ function updateSort(field) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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() !== '') {
|
||||
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');
|
||||
|
||||
// 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 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
|
||||
@@ -420,6 +625,10 @@ function showSourceDetails(sourceId) {
|
||||
data.objitems.forEach(objitem => {
|
||||
const row = document.createElement('tr');
|
||||
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>${objitem.frequency}</td>
|
||||
@@ -434,6 +643,9 @@ function showSourceDetails(sourceId) {
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Setup modal select-all checkbox
|
||||
setupModalSelectAll();
|
||||
} else {
|
||||
// Show no data message
|
||||
document.getElementById('modalNoData').style.display = 'block';
|
||||
@@ -449,5 +661,32 @@ function showSourceDetails(sourceId) {
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
189
dbapp/mainapp/templates/mainapp/source_map.html
Normal file
189
dbapp/mainapp/templates/mainapp/source_map.html
Normal file
@@ -0,0 +1,189 @@
|
||||
{% extends "mainapp/base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Карта источников{% endblock title %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Leaflet CSS -->
|
||||
<link href="{% static 'leaflet/leaflet.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-measure/leaflet-measure.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-tree/L.Control.Layers.Tree.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
#map {
|
||||
position: fixed;
|
||||
top: 56px; /* Высота navbar */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.legend {
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
||||
font-size: 11px;
|
||||
}
|
||||
.legend h6 {
|
||||
font-size: 12px;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
.legend-item {
|
||||
margin: 3px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.legend-marker {
|
||||
width: 18px;
|
||||
height: 30px;
|
||||
margin-right: 6px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="map"></div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Leaflet JavaScript -->
|
||||
<script src="{% static 'leaflet/leaflet.js' %}"></script>
|
||||
<script src="{% static 'leaflet-measure/leaflet-measure.ru.js' %}"></script>
|
||||
<script src="{% static 'leaflet-tree/L.Control.Layers.Tree.js' %}"></script>
|
||||
|
||||
<script>
|
||||
// Инициализация карты
|
||||
let map = L.map('map').setView([55.75, 37.62], 5);
|
||||
L.control.scale({
|
||||
imperial: false,
|
||||
metric: true
|
||||
}).addTo(map);
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
const street = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
street.addTo(map);
|
||||
|
||||
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||
attribution: 'Tiles © Esri'
|
||||
});
|
||||
|
||||
const street_local = L.tileLayer('http://127.0.0.1:8080/styles/basic-preview/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: 'Local Tiles'
|
||||
});
|
||||
|
||||
const baseLayers = {
|
||||
"Улицы": street,
|
||||
"Спутник": satellite,
|
||||
"Локально": street_local
|
||||
};
|
||||
|
||||
L.control.layers(baseLayers).addTo(map);
|
||||
map.setMaxZoom(18);
|
||||
map.setMinZoom(0);
|
||||
L.control.measure({ primaryLengthUnit: 'kilometers' }).addTo(map);
|
||||
|
||||
// Цвета для маркеров
|
||||
var markerColors = {
|
||||
'blue': 'blue',
|
||||
'orange': 'orange',
|
||||
'green': 'green',
|
||||
'violet': 'violet'
|
||||
};
|
||||
|
||||
var getColorIcon = function(color) {
|
||||
return L.icon({
|
||||
iconUrl: '{% static "leaflet-markers/img/marker-icon-" %}' + color + '.png',
|
||||
shadowUrl: '{% static "leaflet-markers/img/marker-shadow.png" %}',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
};
|
||||
|
||||
var overlays = [];
|
||||
|
||||
// Создаём слои для каждого типа координат
|
||||
{% for group in groups %}
|
||||
var groupName = '{{ group.name|escapejs }}';
|
||||
var colorName = '{{ group.color }}';
|
||||
var groupIcon = getColorIcon(colorName);
|
||||
var groupLayer = L.layerGroup();
|
||||
|
||||
var subgroup = [];
|
||||
{% for point_data in group.points %}
|
||||
var pointName = "{{ point_data.source_id|escapejs }}";
|
||||
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
|
||||
icon: groupIcon
|
||||
}).bindPopup(pointName);
|
||||
groupLayer.addLayer(marker);
|
||||
|
||||
subgroup.push({
|
||||
label: "{{ forloop.counter }} - {{ point_data.source_id|escapejs }}",
|
||||
layer: marker
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
overlays.push({
|
||||
label: groupName,
|
||||
selectAllCheckbox: true,
|
||||
children: subgroup,
|
||||
layer: groupLayer
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
// Корневая группа
|
||||
const rootGroup = {
|
||||
label: "Все точки",
|
||||
selectAllCheckbox: true,
|
||||
children: overlays,
|
||||
layer: L.layerGroup()
|
||||
};
|
||||
|
||||
// Создаём tree control
|
||||
const layerControl = L.control.layers.tree(baseLayers, [rootGroup], {
|
||||
collapsed: false,
|
||||
autoZIndex: true
|
||||
});
|
||||
layerControl.addTo(map);
|
||||
|
||||
// Подгоняем карту под все маркеры
|
||||
{% if groups %}
|
||||
var groupBounds = L.featureGroup([]);
|
||||
{% for group in groups %}
|
||||
{% for point_data in group.points %}
|
||||
groupBounds.addLayer(L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}]));
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
map.fitBounds(groupBounds.getBounds().pad(0.1));
|
||||
{% endif %}
|
||||
|
||||
// Добавляем легенду в левый нижний угол
|
||||
var legend = L.control({ position: 'bottomleft' });
|
||||
legend.onAdd = function(map) {
|
||||
var div = L.DomUtil.create('div', 'legend');
|
||||
div.innerHTML = '<h6><strong>Легенда</strong></h6>';
|
||||
|
||||
{% for group in groups %}
|
||||
div.innerHTML += `
|
||||
<div class="legend-item">
|
||||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-" %}{{ group.color }}.png');"></div>
|
||||
<span>{{ group.name|escapejs }}</span>
|
||||
</div>
|
||||
`;
|
||||
{% endfor %}
|
||||
|
||||
return div;
|
||||
};
|
||||
legend.addTo(map);
|
||||
</script>
|
||||
{% endblock extra_js %}
|
||||
244
dbapp/mainapp/templates/mainapp/source_with_points_map.html
Normal file
244
dbapp/mainapp/templates/mainapp/source_with_points_map.html
Normal file
@@ -0,0 +1,244 @@
|
||||
{% extends "mainapp/base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Карта источника #{{ source_id }} с точками{% endblock title %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Leaflet CSS -->
|
||||
<link href="{% static 'leaflet/leaflet.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-measure/leaflet-measure.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'leaflet-tree/L.Control.Layers.Tree.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
#map {
|
||||
position: fixed;
|
||||
top: 56px; /* Высота navbar */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.legend {
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
||||
font-size: 11px;
|
||||
}
|
||||
.legend h6 {
|
||||
font-size: 12px;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
.legend-item {
|
||||
margin: 3px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.legend-marker {
|
||||
width: 18px;
|
||||
height: 30px;
|
||||
margin-right: 6px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.legend-section {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.legend-section:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
.legend-section-title {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="map"></div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Leaflet JavaScript -->
|
||||
<script src="{% static 'leaflet/leaflet.js' %}"></script>
|
||||
<script src="{% static 'leaflet-measure/leaflet-measure.ru.js' %}"></script>
|
||||
<script src="{% static 'leaflet-tree/L.Control.Layers.Tree.js' %}"></script>
|
||||
|
||||
<script>
|
||||
// Инициализация карты
|
||||
let map = L.map('map').setView([55.75, 37.62], 5);
|
||||
L.control.scale({
|
||||
imperial: false,
|
||||
metric: true
|
||||
}).addTo(map);
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
const street = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
street.addTo(map);
|
||||
|
||||
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||
attribution: 'Tiles © Esri'
|
||||
});
|
||||
|
||||
const street_local = L.tileLayer('http://127.0.0.1:8080/styles/basic-preview/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: 'Local Tiles'
|
||||
});
|
||||
|
||||
const baseLayers = {
|
||||
"Улицы": street,
|
||||
"Спутник": satellite,
|
||||
"Локально": street_local
|
||||
};
|
||||
|
||||
L.control.layers(baseLayers).addTo(map);
|
||||
map.setMaxZoom(18);
|
||||
map.setMinZoom(0);
|
||||
L.control.measure({ primaryLengthUnit: 'kilometers' }).addTo(map);
|
||||
|
||||
// Функция для создания иконки
|
||||
var getColorIcon = function(color) {
|
||||
return L.icon({
|
||||
iconUrl: '{% static "leaflet-markers/img/marker-icon-" %}' + color + '.png',
|
||||
shadowUrl: '{% static "leaflet-markers/img/marker-shadow.png" %}',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
};
|
||||
|
||||
var sourceOverlays = [];
|
||||
var glPointLayers = [];
|
||||
|
||||
// Создаём слои для координат источника и точек ГЛ
|
||||
{% for group in groups %}
|
||||
var groupName = '{{ group.name|escapejs }}';
|
||||
var colorName = '{{ group.color }}';
|
||||
var groupIcon = getColorIcon(colorName);
|
||||
var groupLayer = L.layerGroup();
|
||||
|
||||
{% 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
|
||||
}).bindPopup(pointName);
|
||||
groupLayer.addLayer(marker);
|
||||
{% else %}
|
||||
// Это точка ГЛ
|
||||
var pointName = "{{ point_data.name|escapejs }}";
|
||||
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
|
||||
icon: groupIcon
|
||||
}).bindPopup(pointName + '<br>' + "{{ point_data.frequency|escapejs }}");
|
||||
groupLayer.addLayer(marker);
|
||||
|
||||
// Добавляем каждую точку ГЛ отдельно в список
|
||||
glPointLayers.push({
|
||||
label: "{{ forloop.counter }} - {{ point_data.name|escapejs }} ({{ point_data.frequency|escapejs }})",
|
||||
layer: marker
|
||||
});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// Для координат источника добавляем как отдельный слой без вложенности
|
||||
{% if group.color in 'blue,orange,green,violet' %}
|
||||
sourceOverlays.push({
|
||||
label: groupName,
|
||||
layer: groupLayer
|
||||
});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// Создаём иерархию
|
||||
var treeOverlays = [];
|
||||
|
||||
if (sourceOverlays.length > 0) {
|
||||
treeOverlays.push({
|
||||
label: "Координаты источника #{{ source_id }}",
|
||||
selectAllCheckbox: true,
|
||||
children: sourceOverlays,
|
||||
layer: L.layerGroup()
|
||||
});
|
||||
}
|
||||
|
||||
if (glPointLayers.length > 0) {
|
||||
treeOverlays.push({
|
||||
label: "Точки ГЛ",
|
||||
selectAllCheckbox: true,
|
||||
children: glPointLayers,
|
||||
layer: L.layerGroup()
|
||||
});
|
||||
}
|
||||
|
||||
// Создаём tree control
|
||||
const layerControl = L.control.layers.tree(baseLayers, treeOverlays, {
|
||||
collapsed: false,
|
||||
autoZIndex: true
|
||||
});
|
||||
layerControl.addTo(map);
|
||||
|
||||
// Подгоняем карту под все маркеры
|
||||
{% if groups %}
|
||||
var groupBounds = L.featureGroup([]);
|
||||
{% for group in groups %}
|
||||
{% for point_data in group.points %}
|
||||
groupBounds.addLayer(L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}]));
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
map.fitBounds(groupBounds.getBounds().pad(0.1));
|
||||
{% endif %}
|
||||
|
||||
// Добавляем легенду в левый нижний угол
|
||||
var legend = L.control({ position: 'bottomleft' });
|
||||
legend.onAdd = function(map) {
|
||||
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>';
|
||||
hasSourceCoords = true;
|
||||
}
|
||||
{% endif %}
|
||||
div.innerHTML += `
|
||||
<div class="legend-item">
|
||||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-" %}{{ group.color }}.png');"></div>
|
||||
<span>{{ group.name|escapejs }}</span>
|
||||
</div>
|
||||
`;
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// Точки ГЛ (все одним цветом)
|
||||
{% for group in groups %}
|
||||
{% if group.color not in 'blue,orange,green,violet' %}
|
||||
div.innerHTML += '<div class="legend-section"><div class="legend-section-title">Точки ГЛ:</div></div>';
|
||||
div.innerHTML += `
|
||||
<div class="legend-item">
|
||||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-" %}{{ group.color }}.png');"></div>
|
||||
<span>{{ group.name|escapejs }}</span>
|
||||
</div>
|
||||
`;
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
return div;
|
||||
};
|
||||
legend.addTo(map);
|
||||
</script>
|
||||
{% endblock extra_js %}
|
||||
Reference in New Issue
Block a user