обновление для Кубка России
переделаны все парсеры на ссылки из базы
This commit is contained in:
@@ -107,6 +107,82 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.parser-import-card {
|
||||
width: 100%;
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(0, 255, 136, 0.18);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
.parser-import-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.parser-import-subtitle {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.parser-import-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.parser-checkboxes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.parser-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: #0f141d;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.parser-checkbox input {
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.parser-submit-row {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.parser-mode-note {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.parser-import-grid,
|
||||
.parser-checkboxes {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.log-block {
|
||||
margin-top: 20px;
|
||||
border: 1px solid var(--border);
|
||||
@@ -151,7 +227,8 @@
|
||||
}
|
||||
|
||||
.account-modal,
|
||||
.update-modal {
|
||||
.update-modal,
|
||||
.settings-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: none;
|
||||
@@ -163,12 +240,14 @@
|
||||
}
|
||||
|
||||
.account-modal.active,
|
||||
.update-modal.active {
|
||||
.update-modal.active,
|
||||
.settings-modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.account-modal-card,
|
||||
.update-modal-card {
|
||||
.update-modal-card,
|
||||
.settings-modal-card {
|
||||
width: min(100%, 460px);
|
||||
background: rgba(12, 17, 26, 0.98);
|
||||
border: 1px solid rgba(0, 255, 136, 0.28);
|
||||
@@ -177,21 +256,24 @@
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.update-modal-card {
|
||||
.update-modal-card,
|
||||
.settings-modal-card {
|
||||
width: min(100%, 760px);
|
||||
max-height: 92vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.account-modal-title,
|
||||
.update-modal-title {
|
||||
.update-modal-title,
|
||||
.settings-modal-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.account-modal-subtitle,
|
||||
.update-modal-subtitle {
|
||||
.update-modal-subtitle,
|
||||
.settings-modal-subtitle {
|
||||
color: var(--muted);
|
||||
margin-bottom: 18px;
|
||||
line-height: 1.5;
|
||||
@@ -202,6 +284,46 @@
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.env-groups {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.env-group {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
.env-group-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.env-group-subtitle {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.env-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.env-field-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.env-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
@@ -407,24 +529,66 @@
|
||||
</div>
|
||||
|
||||
<div class="parser-actions">
|
||||
<form method="post" action="/admin/db/run-parser" class="action-form" data-parser-title="Игроки" data-parser-status="Сбор и сохранение базы игроков...">
|
||||
<input type="hidden" name="parser_name" value="players">
|
||||
<button type="submit" class="action-btn">🗄️ Заграбить игроков</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/db/run-parser" class="action-form parser-import-card" data-parser-title="Выбранные данные" data-parser-status="Загрузка выбранных данных из выбранного турнира...">
|
||||
<div class="parser-import-title">Импорт данных</div>
|
||||
<div class="parser-import-subtitle">
|
||||
Выберите источник турнира и отметьте, какие данные нужно заграбить. Существующие команды, игроки и матчи будут обновлены через UPSERT, без полной очистки таблиц.
|
||||
</div>
|
||||
|
||||
<form method="post" action="/admin/db/run-parser" class="action-form" data-parser-title="Расписание" data-parser-status="Загрузка расписания матчей с сайта...">
|
||||
<input type="hidden" name="parser_name" value="schedule">
|
||||
<button type="submit" class="action-btn">🗄️ Заграбить расписание</button>
|
||||
</form>
|
||||
<div class="parser-import-grid">
|
||||
<div>
|
||||
<label class="field-label" for="parserSource">Источник / турнир</label>
|
||||
{% set sources = parser_sources.values() if parser_sources is mapping else parser_sources|default([]) %}
|
||||
<select class="field-select" id="parserSource" name="parser_source" required>
|
||||
{% if sources %}
|
||||
{% for source in sources %}
|
||||
<option value="{{ source.key }}" data-logo-base-path="{{ source.logo_base_path or '' }}" data-photo-base-path="{{ source.photo_base_path or '' }}" {% if source.key == default_parser_source_key %}selected{% endif %}>
|
||||
{{ source.title }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="SUPERLEAGUE" data-logo-base-path="D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Teams Logos" data-photo-base-path="D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo" selected>Суперлига 2026</option>
|
||||
<option value="RUSSIAN_CUP" data-logo-base-path="D:\Графика\ФУТБОЛ\Кубок России 2026\Teams Logos" data-photo-base-path="D:\Графика\ФУТБОЛ\Кубок России 2026\Photo">Кубок России 2026</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
<div class="helper-text">Список источников хранится в <b>базе данных</b> и редактируется в настройках проекта.</div>
|
||||
<div class="helper-text">Путь к логотипам: <b id="selectedLogoBasePath">—</b></div>
|
||||
<div class="helper-text">Путь к фотографиям: <b id="selectedPhotoBasePath">—</b></div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/admin/db/run-parser" class="action-form" data-parser-title="Турнирка" data-parser-status="Обновление турнирной таблицы...">
|
||||
<input type="hidden" name="parser_name" value="standings">
|
||||
<button type="submit" class="action-btn">🗄️ Заграбить турнирку</button>
|
||||
<div>
|
||||
<label class="field-label">Что парсить</label>
|
||||
<div class="parser-checkboxes">
|
||||
<label class="parser-checkbox">
|
||||
<input type="checkbox" name="parser_names" value="teams" checked>
|
||||
<span>Команды</span>
|
||||
</label>
|
||||
<label class="parser-checkbox">
|
||||
<input type="checkbox" name="parser_names" value="players" checked>
|
||||
<span>Игроки</span>
|
||||
</label>
|
||||
<label class="parser-checkbox">
|
||||
<input type="checkbox" name="parser_names" value="schedule" checked>
|
||||
<span>Расписание</span>
|
||||
</label>
|
||||
<label class="parser-checkbox">
|
||||
<input type="checkbox" name="parser_names" value="standings">
|
||||
<span>Турнирка</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="parser-submit-row">
|
||||
<button type="submit" class="action-btn">🗄️ Заграбить выбранное</button>
|
||||
<div class="parser-mode-note">Безопасный режим: добавляет новые записи и обновляет существующие. Турнирка по-прежнему пересобирается только за выбранный сезон.</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="account-actions">
|
||||
<button type="button" class="account-toggle-btn" id="openCreateAccountBtn">➕ Создать аккаунт</button>
|
||||
<button type="button" class="account-toggle-btn" id="openSettingsBtn">⚙️ Настройки проекта</button>
|
||||
<button type="button" class="account-toggle-btn" id="openAddUpdateBtn">📝 Добавить обновление</button>
|
||||
</div>
|
||||
|
||||
@@ -458,6 +622,21 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if settings_output %}
|
||||
<div class="log-block {% if settings_success %}success{% else %}error{% endif %}">
|
||||
<div class="log-title">Сохранение настроек проекта</div>
|
||||
<div class="log-status">
|
||||
Статус:
|
||||
{% if settings_success %}
|
||||
успешно
|
||||
{% else %}
|
||||
ошибка
|
||||
{% endif %}
|
||||
</div>
|
||||
<pre class="log-output">{{ settings_output }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if update_output %}
|
||||
<div class="log-block {% if update_success %}success{% else %}error{% endif %}">
|
||||
<div class="log-title">Добавление обновления</div>
|
||||
@@ -510,6 +689,58 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div id="settingsModal" class="settings-modal" aria-hidden="true">
|
||||
<div class="settings-modal-card">
|
||||
<div class="settings-modal-title">Настройки проекта</div>
|
||||
<div class="settings-modal-subtitle">
|
||||
Здесь редактируются источники парсинга: ссылки, сезон, тип календаря, папки логотипов и фотографий. Эти данные хранятся в базе данных.
|
||||
<br>.env оставляем только для NAS и подключения к базе.
|
||||
</div>
|
||||
|
||||
<form method="post" action="/admin/db/project-settings" id="settingsForm">
|
||||
{% if project_settings and project_settings.groups %}
|
||||
<div class="env-groups">
|
||||
{% for group in project_settings.groups %}
|
||||
<div class="env-group">
|
||||
<div class="env-group-title">{{ group.title }}</div>
|
||||
{% if group.subtitle %}
|
||||
<div class="env-group-subtitle">{{ group.subtitle }}</div>
|
||||
{% endif %}
|
||||
<div class="env-grid">
|
||||
{% for field in group.fields %}
|
||||
<div class="{% if field.wide %}env-field-wide{% endif %}">
|
||||
<label class="field-label" for="env_{{ field.key }}">{{ field.label }}</label>
|
||||
{% if field.type == 'select' %}
|
||||
<select class="field-select" id="env_{{ field.key }}" name="{{ field.key }}">
|
||||
{% for option in field.options %}
|
||||
<option value="{{ option.value }}" {% if option.value == field.value %}selected{% endif %}>{{ option.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input class="field-input" id="env_{{ field.key }}" type="text" name="{{ field.key }}" value="{{ field.value }}">
|
||||
{% endif %}
|
||||
{% if field.help %}
|
||||
<div class="helper-text">{{ field.help }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="helper-text">Не удалось загрузить список настроек проекта из базы данных.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="cancel-btn" id="closeSettingsBtn">Отмена</button>
|
||||
<button type="submit" class="submit-btn">Сохранить настройки</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="updateModal" class="update-modal" aria-hidden="true">
|
||||
<div class="update-modal-card">
|
||||
<div class="update-modal-title">Добавить обновление</div>
|
||||
@@ -617,9 +848,15 @@
|
||||
const canvas = document.getElementById("matrixCanvas");
|
||||
const ctx = canvas ? canvas.getContext("2d") : null;
|
||||
const parserForms = document.querySelectorAll(".action-form");
|
||||
const parserSourceSelect = document.getElementById("parserSource");
|
||||
const selectedLogoBasePath = document.getElementById("selectedLogoBasePath");
|
||||
const selectedPhotoBasePath = document.getElementById("selectedPhotoBasePath");
|
||||
const openCreateAccountBtn = document.getElementById("openCreateAccountBtn");
|
||||
const closeCreateAccountBtn = document.getElementById("closeCreateAccountBtn");
|
||||
const accountModal = document.getElementById("accountModal");
|
||||
const openSettingsBtn = document.getElementById("openSettingsBtn");
|
||||
const closeSettingsBtn = document.getElementById("closeSettingsBtn");
|
||||
const settingsModal = document.getElementById("settingsModal");
|
||||
const openAddUpdateBtn = document.getElementById("openAddUpdateBtn");
|
||||
const closeAddUpdateBtn = document.getElementById("closeAddUpdateBtn");
|
||||
const updateModal = document.getElementById("updateModal");
|
||||
@@ -695,6 +932,18 @@
|
||||
accountModal.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
function openSettingsModal() {
|
||||
if (!settingsModal) return;
|
||||
settingsModal.classList.add("active");
|
||||
settingsModal.setAttribute("aria-hidden", "false");
|
||||
}
|
||||
|
||||
function closeSettingsModal() {
|
||||
if (!settingsModal) return;
|
||||
settingsModal.classList.remove("active");
|
||||
settingsModal.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
function openUpdateModal() {
|
||||
if (!updateModal) return;
|
||||
if (updateDate && !updateDate.value) {
|
||||
@@ -742,6 +991,17 @@
|
||||
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||||
}
|
||||
|
||||
function updateSelectedSourcePaths() {
|
||||
if (!parserSourceSelect) return;
|
||||
const option = parserSourceSelect.options[parserSourceSelect.selectedIndex];
|
||||
if (selectedLogoBasePath) {
|
||||
selectedLogoBasePath.textContent = option?.dataset?.logoBasePath || "—";
|
||||
}
|
||||
if (selectedPhotoBasePath) {
|
||||
selectedPhotoBasePath.textContent = option?.dataset?.photoBasePath || "—";
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const button = event.target.closest(".format-btn");
|
||||
if (!button) return;
|
||||
@@ -757,6 +1017,9 @@
|
||||
insertSnippet(textarea, snippet);
|
||||
});
|
||||
|
||||
updateSelectedSourcePaths();
|
||||
parserSourceSelect?.addEventListener("change", updateSelectedSourcePaths);
|
||||
|
||||
parserForms.forEach((form) => {
|
||||
form.addEventListener("submit", () => {
|
||||
const parserTitle = form.dataset.parserTitle || "Запуск парсера";
|
||||
@@ -786,6 +1049,22 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (openSettingsBtn) {
|
||||
openSettingsBtn.addEventListener("click", openSettingsModal);
|
||||
}
|
||||
|
||||
if (closeSettingsBtn) {
|
||||
closeSettingsBtn.addEventListener("click", closeSettingsModal);
|
||||
}
|
||||
|
||||
if (settingsModal) {
|
||||
settingsModal.addEventListener("click", (event) => {
|
||||
if (event.target === settingsModal) {
|
||||
closeSettingsModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (openAddUpdateBtn) {
|
||||
openAddUpdateBtn.addEventListener("click", openUpdateModal);
|
||||
}
|
||||
@@ -809,6 +1088,7 @@
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeAccountModal();
|
||||
closeSettingsModal();
|
||||
closeUpdateModal();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -229,13 +229,13 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group full">
|
||||
<label>Фото игрока</label>
|
||||
<input type="text" name="photo" value="{{ player.photo }}">
|
||||
<label>Файл фото игрока</label>
|
||||
<input type="text" name="photo" value="{{ player.photo }}" placeholder="Например: Динамо\Иванов Иван.png или Иванов Иван.png">
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" name="photo_enabled" value="true" {% if player.photo_enabled %}checked{% endif %}>
|
||||
Использовать фото в JSON/vMix
|
||||
</label>
|
||||
<div class="hint">Если галочка включена, путь к фото в JSON генерируется по старой схеме: Photo\Команда\Фамилия Имя.png. Если выключена — отдается EMPTY.png.</div>
|
||||
<div class="hint">Если поле пустое, путь генерируется автоматически: папка фотографий источника + Команда\Фамилия Имя.png. Если поле заполнено, можно указать имя файла или относительный путь. Если галочка выключена — отдается EMPTY.png из папки выбранного источника.</div>
|
||||
{% if player.photo %}
|
||||
<div class="media-preview-box">
|
||||
<img src="{{ player.photo }}" alt="{{ player.full_name }}" class="media-preview-image">
|
||||
|
||||
@@ -27,19 +27,6 @@
|
||||
background: var(--panel-2);
|
||||
color: var(--text);
|
||||
}
|
||||
.logo-box {
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-2);
|
||||
}
|
||||
.logo-preview {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.actions { margin-top: 20px; display: flex; gap: 10px; }
|
||||
.btn { min-height: 42px; padding: 0 14px; border: none; border-radius: 10px; cursor: pointer; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
@@ -80,14 +67,10 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group full">
|
||||
<label>Путь к логотипу</label>
|
||||
<input type="text" name="logo_path" value="{{ team.logo_path }}">
|
||||
<div class="logo-box">
|
||||
{% if team.logo_path %}
|
||||
<img src="{{ team.logo_path }}" alt="{{ team.name }}" class="logo-preview">
|
||||
{% else %}
|
||||
Логотип не задан.
|
||||
{% endif %}
|
||||
<label>Файл логотипа</label>
|
||||
<input type="text" name="logo_path" value="{{ team.logo_path }}" placeholder="Например: Динамо_Синий.png">
|
||||
<div class="small-meta">
|
||||
В команде хранится только имя файла. Полный путь подставляется автоматически по источнику выбранного матча.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,13 +26,6 @@
|
||||
tr:last-child td { border-bottom: none; }
|
||||
.id-col { width: 70px; }
|
||||
.action-col { width: 140px; text-align: right; }
|
||||
.logo-preview {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: contain;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.empty-box { padding: 18px; border-radius: 12px; background: var(--panel-2); color: var(--muted); border: 1px dashed var(--border); }
|
||||
</style>
|
||||
</head>
|
||||
@@ -57,7 +50,7 @@
|
||||
<th>Полное название</th>
|
||||
<th>Город</th>
|
||||
<th>3 буквы</th>
|
||||
<th>Логотип</th>
|
||||
<th>Файл логотипа</th>
|
||||
<th>External ID</th>
|
||||
<th class="action-col"></th>
|
||||
</tr>
|
||||
@@ -70,13 +63,7 @@
|
||||
<td>{{ t.full_name or "—" }}</td>
|
||||
<td>{{ t.city or "—" }}</td>
|
||||
<td>{{ t.short_name_3 or "—" }}</td>
|
||||
<td>
|
||||
{% if t.logo_path %}
|
||||
<img src="{{ t.logo_path }}" alt="{{ t.name }}" class="logo-preview">
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ t.logo_path or "—" }}</td>
|
||||
<td>{{ t.external_id }}</td>
|
||||
<td class="action-col">
|
||||
<a href="/admin/db/teams/{{ t.id }}/edit" class="btn btn-secondary">Редактировать</a>
|
||||
|
||||
Reference in New Issue
Block a user