# Анализ Legacy Devices Panel ## Обзор legacy проекта Расположение: `/home/user/work/luckfox/mybeacon-legacy/www/my-wifi/panel/` Технологии: - **Backend**: PHP 5.x/7.x + MySQL (MariaDB) - **Frontend**: jQuery + DataTables + jEditable - **Стиль**: Bootstrap 3 + кастомная тема ## Структура БД (Legacy) ### Таблица `configs` (устройства) ```sql CREATE TABLE `configs` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `time` timestamp DEFAULT current_timestamp(), -- Last seen `mac` varchar(40) UNIQUE NOT NULL, `wf_client_ssid` varchar(60), -- WiFi SSID для подключения `wf_client_psk` varchar(60), -- WiFi пароль `ovpn_flag` tinyint(1), -- OpenVPN включен `ovpn_addr` varchar(60), -- URL для получения VPN конфига `wf_flag` tinyint(1), -- WiFi сбор включен `wf_addr` varchar(60), -- URL для отправки WiFi данных `bt_flag` tinyint(1), -- BLE сбор включен `bt_addr` varchar(60), -- URL для отправки BLE данных `fw_flag` tinyint(1), -- Firmware update включен `fw_addr` varchar(60), -- URL для получения firmware `reboot_flag` tinyint(1), -- Флаг перезагрузки `ip` varchar(40) -- IP адрес устройства ); ``` ### Таблица `users` (клиенты/организации) ```sql CREATE TABLE `users` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `login` text NOT NULL, `password` text NOT NULL, `device` text NOT NULL, -- MAC-адреса через ";" (b8:27:eb:c1:46:0e;cc:2d:e0:ca:9f:7e;) `addTime` timestamp DEFAULT current_timestamp(), `dopinfo` text, -- Доп. информация о клиенте `paidTill` text, -- Текст об оплате `folder` text, -- Папка для файлов (не использовалось) `till` date DEFAULT '2022-05-03', -- Дата оплаты до `dopfolder` varchar(128) -- Доп. папка (не использовалось) ); ``` ### Таблица `user_devices` (связь) ```sql CREATE TABLE `user_devices` ( `mac` varchar(40), `name` varchar(255), `user_id` int(11), `max1000` tinyint(1), -- Лимит (0=оплачен, 1=лимит 1000) `segment` tinyint(1) -- Для Yandex.Аудитории ); ``` ## Навигация (Legacy) ``` 1. Устройства (index.php) - главная страница, список всех устройств 2. Добавление (adddev.php) - форма добавления клиента с устройствами 3. Клиенты (clients.php) - список клиентов 4. Инструкция (info.php) - документация 5. VPN (внешняя ссылка) - сканер VPN подключений ``` ## Функционал Devices Panel (index.php) ### Таблица устройств **Колонки:** 1. `#` - порядковый номер 2. `MAC-адрес` - читаемый (не редактируемый) 3. `время` - last seen (не редактируемый) 4. `wf_ssid` - **РЕДАКТИРУЕМАЯ** (inline editing) 5. `wf_psk` - **РЕДАКТИРУЕМАЯ** 6. `ovpn_flag` - **РЕДАКТИРУЕМАЯ** 7. `ovpn_addr` - **РЕДАКТИРУЕМАЯ** 8. `wf_flag` - **РЕДАКТИРУЕМАЯ** 9. `wf_addr` - **РЕДАКТИРУЕМАЯ** 10. `bt_flag` - **РЕДАКТИРУЕМАЯ** 11. `bt_addr` - **РЕДАКТИРУЕМАЯ** 12. `fw_flag` - **РЕДАКТИРУЕМАЯ** 13. `fw_addr` - **РЕДАКТИРУЕМАЯ** 14. `reboot_flag` - **РЕДАКТИРУЕМАЯ** 15. `ip` - **РЕДАКТИРУЕМАЯ** ### Ключевая фича: Inline Editing **Библиотека**: [jEditable](http://www.appelsiini.net/projects/jeditable) **Как работает:** 1. Пользователь кликает на ячейку таблицы (с классом `.editable-td`) 2. Ячейка превращается в `` с кнопками "Ok" и "Cancel" 3. При нажатии "Ok" отправляется AJAX запрос: ```javascript $.get('savewf.php', { p1: value, // новое значение p2: $(this).attr('id'), // ID ячейки (имя поля) p3: $(this).parent('tr').attr('id') // ID строки (id записи) }) ``` 4. Backend (`savewf.php`) выполняет SQL: ```sql UPDATE `configs` SET `{field_name}` = '{value}' WHERE `id`='{id}' ``` 5. Ячейка возвращается к обычному виду с новым значением ### Поиск и фильтрация **Библиотека**: [DataTables](https://datatables.net/) **Функционал:** - Глобальный поиск по **всем** колонкам таблицы - Пользователь вводит любой текст в поле поиска - DataTables фильтрует строки в реальном времени - Работает по: MAC, SSID, IP, адресам, времени, флагам **Код:** ```javascript var editableTable = exampleDatatable.dataTable({ order: [[ 1, 'desc' ]], // Сортировка по ID по убыванию columnDefs: [ { orderable: false, targets: [ 0 ] } ] }); $('.dataTables_filter input').attr('placeholder', 'Search'); ``` ### Сортировка - Сортировка по любой колонке (кроме первой) - По умолчанию: по ID (времени регистрации) по убыванию ## Функционал Clients Panel (clients.php) ### Таблица клиентов **Колонки:** 1. `#` - порядковый номер 2. `Логин` 3. `Пароль` 4. `Устройства` - MAC-адреса через `
` (разделены по `;`) 5. `Оплачено до` - ссылка на редактирование даты 6. `Информация` - допинфо о клиенте 7. `Оплата` - текст статуса оплаты ### Особенности - Список устройств выводится вертикально (`implode('
', explode(';', $line['device']))`) - Ссылка на редактирование даты: `{till}` - Нет inline editing (в отличие от devices) - Только чтение данных ## Функционал Add Device (adddev.php) ### Форма создания клиента **Поля:** 1. `Логин` - required 2. `Пароль` - required 3. `Список устройств` - textarea, через `;` в конце Пример: `d8:0d:17:5e:07:94;ac:84:c6:42:17:90;` 4. `Информация о клиенте` - textarea (юрлицо, имя, телефон, email) 5. `Оплата` - textarea (текст до какого числа оплачено) 6. `Оплачен` - checkbox (max1000: 0=оплачен, 1=лимит) ### Логика создания ```php // 1. Валидация if (login == null || password == null || device == null) { error("Поля логин, пароль и устройства не могут быть пустыми"); } // 2. Проверка существования логина $query = "SELECT * FROM `users` WHERE `login` = '{login}'"; if (row_count > 0) { error("Логин уже занят"); } // 3. Создание пользователя INSERT INTO `users` (login, password, device, dopinfo, paidTill, till) VALUES ('{login}', '{password}', '{device}', '{dopinfo}', '{paidTill}', CURDATE()); $user_id = mysqli_insert_id(); // 4. Создание связей user_devices $macs = explode(";", device); foreach ($macs as $mac) { INSERT INTO `user_devices` (mac, name, user_id, max1000, segment) VALUES ('{mac}', '{mac}', {user_id}, {max1000}, 0); } ``` ## Проблемы Legacy кода ### Безопасность 1. **SQL Injection** - прямая конкатенация в запросах: ```php $query = "UPDATE `configs` SET `".$_REQUEST['p2']."` = '".$_REQUEST['p1']."' WHERE `id`='".$_REQUEST['p3']."'"; ``` 2. **XSS** - нет экранирования вывода 3. **Пароли в открытом виде** - хранятся как plain text 4. **Нет CSRF защиты** 5. **Нет валидации типов данных** ### Архитектура 1. **Нет разделения ролей** - все пользователи видят всё 2. **Нет multi-tenant изоляции** - одна таблица на всех 3. **Нет аутентификации в админке** - доступ без проверки 4. **Хардкод credentials** в каждом файле: ```php $dbname = 'wifi'; $dbuser = 'p328882_wifi'; $dbpass = '0354c0598ld'; ``` 5. **Связь many-to-many через текст** - MAC-адреса через `;` ### UX 1. **Inline editing опасен** - легко случайно изменить данные 2. **Нет подтверждения удаления** 3. **Нет истории изменений** (audit logs) 4. **Нет валидации MAC-адресов** 5. **Нет статусов устройств** (online/offline/error) ## Анализ требований для нового проекта ### Что взять из Legacy #### 1. **Универсальный поиск по всем полям** ⭐ КЛЮЧЕВАЯ ФИЧА **Почему важно:** - Быстро найти устройство по MAC - Найти устройства организации - Найти по IP адресу - Найти по owner/admin email **Реализация в новом проекте:** **Backend:** ```python # backend/app/api/v1/superadmin/devices.py или client/devices.py @router.get("/devices", response_model=DeviceListResponse) async def get_devices( search: Optional[str] = None, organization_id: Optional[int] = None, offset: int = 0, limit: int = 50, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): query = select(Device) # Фильтр по организации (для multi-tenant) if current_user.role != "superadmin": query = query.where(Device.organization_id == current_user.organization_id) elif organization_id: query = query.where(Device.organization_id == organization_id) # Универсальный поиск if search: search_filter = or_( Device.mac_address.ilike(f"%{search}%"), Device.simple_id.cast(String).ilike(f"%{search}%"), Organization.name.ilike(f"%{search}%"), User.email.ilike(f"%{search}%"), User.full_name.ilike(f"%{search}%") ) query = ( query .outerjoin(Organization) .outerjoin(User, User.organization_id == Device.organization_id) .where(search_filter) ) total = await db.scalar(select(func.count()).select_from(query.subquery())) devices = await db.scalars(query.offset(offset).limit(limit)) return DeviceListResponse(devices=devices.all(), total=total) ``` **Frontend (Vue 3):** ```vue ``` **Переводы (i18n):** ```javascript // frontend/src/i18n/index.js const messages = { ru: { devices: { searchPlaceholder: 'Поиск по MAC, организации, владельцу...' } }, en: { devices: { searchPlaceholder: 'Search by MAC, organization, owner...' } } } ``` #### 2. **Быстрый просмотр в таблице** - Компактная таблица с основной информацией - Сортировка по любой колонке - Pagination для больших списков #### 3. **Inline индикаторы** - Статус устройства (online/offline/error) - цветные badges - Дата last_seen с человекочитаемым форматом - Organization name вместо ID ### Что НЕ брать из Legacy #### 1. **Inline Editing** ❌ **Проблемы:** - Случайные изменения (кликнул мимо) - Нет валидации - Нет подтверждения - Нет истории изменений **Решение:** Модальные окна для редактирования (как сейчас) #### 2. **Текстовые связи (MAC через `;`)** ❌ **Проблема:** `device` text NOT NULL - "b8:27:eb:c1:46:0e;cc:2d:e0:ca:9f:7e;" **Решение:** Нормализованная БД с foreign keys (как сейчас) #### 3. **OpenVPN туннели** ❌ **Из ТЗ:** "Сейчас у нас VPN не будет" **Убираем:** - `ovpn_flag` - `ovpn_addr` - VPN страница в навигации #### 4. **Множественные флаги и адреса** ❌ **Legacy имело:** - `wf_flag` + `wf_addr` (WiFi) - `bt_flag` + `bt_addr` (BLE) - `fw_flag` + `fw_addr` (Firmware) **Новый подход:** - Модульность на уровне Organization: `wifi_enabled`, `ble_enabled` - Единый API endpoint вместо хардкод URL в БД - Config как JSONB для гибкости #### 5. **Inline WiFi credentials** ❌ **Legacy:** `wf_client_ssid` и `wf_client_psk` в таблице configs **Проблема:** - Пароли в открытом виде - Одинаковые для всех устройств организации **Решение:** - Credentials на уровне Organization (в зашифрованном виде или в vault) - Device получает через защищенный API ## Новая структура Devices Panel ### Фильтры (обязательно) **Для новой админки:** 1. **Глобальный поиск** (текстовое поле) - По MAC-адресу - По simple_id (#1, #2, #3) - По имени организации - По email владельца организации 2. **Фильтр по организации** (dropdown для superadmin) - "Все организации" - "Не назначены" - Список организаций 3. **Фильтр по статусу** (chips/badges) - Online - Offline - Error 4. **Сортировка** - По Simple ID (по умолчанию) - По дате последнего подключения - По организации - По статусу ### Таблица устройств **Колонки (минимум):** | Колонка | Описание | Для Superadmin | Для Client | |---------|----------|----------------|------------| | Simple ID | #1, #2, #3 | ✅ | ✅ | | MAC Address | `ac:84:c6:d4:9c:c4` | ✅ | ✅ | | Organization | Название организации | ✅ | ❌ (скрыта) | | Owner | Email владельца | ✅ | ✅ | | Status | online/offline/error | ✅ | ✅ | | Last Seen | "2 минуты назад" | ✅ | ✅ | | Actions | Edit, Delete | ✅ | Owner/Admin only | ### Детали устройства (модальное окно или отдельная страница) **Общая информация:** - Simple ID - MAC Address - Organization - Status - Created At - Last Seen At - IP Address (если онлайн) **Конфигурация (JSONB):** ```json { "wifi_enabled": true, "ble_enabled": true, "upload_interval": 300, "scan_interval": 60, "custom_settings": {} } ``` **История подключений:** - Таблица последних 10 подключений - Timestamp, IP, Duration **Действия:** - Изменить организацию (superadmin) - Изменить статус - Удалить устройство (с подтверждением) - Просмотр audit logs ## Сравнение Legacy vs Новый проект | Функция | Legacy | Новый проект | |---------|--------|--------------| | **Аутентификация** | ❌ Нет | ✅ JWT (access + refresh) | | **Multi-tenant** | ❌ Все в одной куче | ✅ Изоляция по organization_id | | **Роли** | ❌ Нет | ✅ 6 ролей (superadmin, owner, admin, manager, operator, viewer) | | **Поиск** | ✅ По всем полям | ✅ По MAC, org, user, simple_id | | **Редактирование** | ✅ Inline (опасно) | ✅ Модальные окна с валидацией | | **Simple ID** | ❌ Нет | ✅ Auto-increment (#1, #2, #3) | | **OpenVPN** | ✅ Было | ❌ Убрали | | **Audit Logs** | ❌ Нет | ✅ Полное логирование | | **Статусы** | ❌ Нет | ✅ online/offline/error | | **Пагинация** | ✅ DataTables | ✅ Backend pagination | | **i18n** | ❌ Только русский | ✅ RU/EN | ## Приоритеты реализации ### Шаг 1: Базовый функционал (уже есть) - ✅ CRUD устройств - ✅ Фильтр по организации - ✅ Статусы - ✅ Last seen ### Шаг 2: Поиск (TODO) - [ ] Универсальное поле поиска - [ ] Backend endpoint с фильтрацией - [ ] Debounce для поиска - [ ] Подсветка результатов ### Шаг 3: Детали устройства (TODO) - [ ] Модальное окно с полной информацией - [ ] История подключений - [ ] Редактирование конфига (JSONB) - [ ] Просмотр audit logs для устройства ### Шаг 4: Улучшения UX (TODO) - [ ] Сортировка таблицы - [ ] Фильтр по статусу (chips) - [ ] Экспорт в CSV/Excel - [ ] Bulk actions (массовое удаление/изменение) ## Выводы ### Ключевые изменения от Legacy 1. **Безопасность на первом месте:** - JWT аутентификация - RBAC система прав - SQL injection защита (SQLAlchemy) - Audit logging 2. **Multi-tenant архитектура:** - Изоляция данных по организациям - Разные уровни доступа 3. **UX улучшения:** - Simple ID вместо длинных MAC - Модальные окна вместо inline edit - Подтверждение опасных действий - i18n поддержка 4. **Современный стек:** - FastAPI вместо PHP - Vue 3 вместо jQuery - PostgreSQL вместо MySQL - REST API вместо прямых SQL запросов ### Что сохранили 1. **Универсальный поиск** - ключевая фича для удобства 2. **Компактная таблица** - быстрый просмотр списка 3. **Статусы и индикаторы** - визуальная обратная связь 4. **Пагинация** - для больших списков ### Что убрали 1. OpenVPN туннели (по ТЗ) 2. Inline editing (небезопасно) 3. Текстовые связи через `;` 4. Хардкод URL в БД 5. WiFi credentials в таблице устройств