#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Vendors report page. Страница управления базой OUI. Бизнес-логика находится в VendorService. Этот модуль отвечает только за HTML-представление. """ from html import escape from .statistics import ( top_vendors, ) from .utils import ( format_datetime as format_report_datetime, ) from .template import ( page_begin, page_end, card, ) # ============================================================ # Helpers # ============================================================ def format_size(size): if not size: return "-" size = float(size) units = ( "B", "KB", "MB", "GB", ) for unit in units: if size < 1024: return f"{size:.1f} {unit}" size /= 1024 return f"{size:.1f} TB" def format_value(value): if value is None or value == "": return "-" return escape( str(value) ) # ============================================================ # Database status # ============================================================ def database_status_block(status): if not status["exists"]: health = "Not found" elif status["healthy"]: health = "Healthy" else: health = "Error" html = f"""
Property Value
Database {format_value(status["database"])}
Status {health}
Total entries {status["total"]}
MA-L {status["ma_l"]}
MA-M {status["ma_m"]}
MA-S {status["ma_s"]}
Schema version {format_value(status["schema_version"])}
Last import {format_report_datetime(status["last_import"])}
Import status {format_value(status["import_status"])}
Size {format_size(status["size"])}
""" return card( "🏭 OUI Database", html, ) # ============================================================ # Top Vendors # ============================================================ def top_vendors_block(conn): rows = top_vendors( conn, limit=15 ) html = """ """ if not rows: html += """ """ else: for vendor, total in rows: if not vendor: vendor = "-" html += f""" """ html += """
Vendor Devices
No vendor data.
{escape(str(vendor))} {total}
""" return card( "🏭 Top Vendors", html, ) # ============================================================ # Vendors page # ============================================================ def update_block( service, preview=None, token=None, job=None ): html = """ """ sources = service.sources mirrors = service.mirrors source_html = """ """ for name, url in sources.items(): mirror = mirrors.get( name ) source_html += f""" """ source_html += """
Source URL Mirror
{escape(str(name))} {escape(str(url))} {escape(str(mirror)) if mirror else "—"}
""" html += """
""" html += card( "🌐 Configured Sources", source_html, ) if preview is None: if job is not None: stage_labels = { "starting": "Запуск обновления", "downloading": "Загрузка источника", "validating": "Проверка источника", "fallback": "Переход на резервное зеркало", "building": "Построение базы OUI", "analyzing": "Анализ изменений", "ready": "Обновление готово", "error": "Ошибка обновления" } stage = job.get( "stage" ) stage_label = stage_labels.get( stage, stage or "Неизвестный этап" ) source = job.get( "source" ) message = job.get( "message" ) error = job.get( "error" ) html += f"""

Обновление базы OUI

Этап: {escape(stage_label)}

Источник: {escape(str(source)) if source else "—"}

{escape(str(message)) if message else ""}

{escape(str(error)) if error else ""}

""" if job.get( "status" ) == "running": html += """

Обновление выполняется. Страница автоматически обновит результат после завершения.

""" elif job.get( "status" ) == "error": html += """
""" html += """
""" else: html += """

Обновление базы OUI

""" html += """
""" return html oui = preview["oui"] build = preview["build"] ap = preview["ap"] ap_total = ( ap["unknown_to_vendor"] + ap["vendor_to_vendor"] + ap["vendor_to_unknown"] ) if oui["identical"]: status_html = """

База OUI не изменилась. Обновление не требуется.

""" elif ap_total == 0: status_html = """

База OUI содержит изменения, но существующие записи AP не требуют изменения vendor.

Обновление базы OUI можно применить отдельно. Оно обновит справочник для будущих определений vendor.

""" else: status_html = """

База OUI содержит изменения. Сначала проверьте изменения в справочнике, затем при необходимости выберите синхронизацию vendor у существующих AP.

""" change_html = f"""
Параметр Значение
Текущих записей {oui["current"]}
Записей в новой базе {oui["candidate"]}
Добавлено {oui["added"]}
Изменено {oui["changed"]}
Удалено {oui["removed"]}
Без изменений {oui["unchanged"]}
Импортировано {build["imported"]}
Пропущено {build["skipped"]}
""" html += card( "🔄 Предварительный просмотр обновления", status_html + change_html, ) html += """ """ if oui["identical"]: pass else: html += f"""
""" if ap_total > 0: html += """

Синхронизация существующих AP

Следующие параметры не изменяют саму базу OUI. Они определяют, какие изменения vendor применить к уже существующим записям WiFi AP.

Изменение vendor Количество Применить
Unknown → Vendor
Что это означает?

Заполнить поле vendor у существующих AP, для которых vendor сейчас неизвестен. Новое значение определяется по обновлённой базе OUI.

""" + str(ap["unknown_to_vendor"]) + """
Vendor → другой Vendor
Что это означает?

Заменить текущее значение vendor, если обновлённая база OUI определяет тот же AP как принадлежащий другому vendor.

""" + str(ap["vendor_to_vendor"]) + """
Vendor → Unknown
Что это означает?

Удалить текущее значение vendor, если обновлённая база OUI больше не позволяет определить vendor для данного AP.

""" + str(ap["vendor_to_unknown"]) + """

Выбрано изменений AP: """ + str( ap["unknown_to_vendor"] ) + """

Синхронизация AP необязательна. Снятые категории не будут изменять существующие записи AP.

""" else: html += """

Синхронизация существующих AP

Существующие записи AP не требуют изменения vendor. Для этого обновления синхронизация AP недоступна.

""" html += """
""" if ap_total > 0: html += f""" """ return html def render_vendors_page( conn, service, preview=None, token=None, message=None, context=None, job=None ): status = service.status() html = page_begin( title="Vendors", description=( "Vendor database and OUI management" ), active="vendors", context=context, ) if message: html += f"""

{escape(message["text"])}

""" html += """
""" html += database_status_block( status ) html += top_vendors_block( conn ) html += """
""" html += update_block( service, preview, token, job ) html += page_end() return html