# Legacy Admin Panel - Анализ ключевых фич ## Расположение `/home/user/work/luckfox/mybeacon-legacy/www/my-wifi/panel/` ## Структура legacy админки ### 3 основные страницы: 1. **Devices (index.php)** - управление устройствами (радарами) 2. **Clients (clients.php)** - управление клиентами (организациями) 3. **Add Device (adddev.php)** - добавление клиента с устройствами --- ## КЛЮЧЕВЫЕ ФИЧИ (без технической реализации) ### 1. Devices Panel - Управление устройствами #### A. Универсальный поиск ⭐ КЛЮЧЕВАЯ ФИЧА **Что делает:** - Пользователь вводит любой текст в одно поле - Система ищет по **всем** колонкам таблицы одновременно - Фильтрация в реальном времени (при вводе) **Примеры поиска:** - `ac:84` → найдет все MAC-адреса содержащие "ac:84" - `192.168` → найдет все устройства с IP в этой подсети - `Office_WiFi` → найдет все устройства подключенные к этому SSID - `beacon.ru` → найдет все устройства с этим адресом отправки данных **Почему важно:** - Админ не знает точный MAC → вводит часть → быстро находит - Админ помнит что "где-то было 192.168..." → вводит → находит - Не нужно помнить в какой колонке искать - Один поисковый запрос вместо 15 фильтров **Решение для нового проекта:** ✅ ТАЩИМ - универсальное поисковое поле с поиском по всем полям --- #### B. Компактная таблица с максимумом информации **Что делает:** - Вся важная информация видна сразу в таблице - Не нужно кликать на строку чтобы увидеть детали - Все 15 полей конфига видны в одной строке **Колонки:** ``` # MAC time wf_ssid wf_psk ovpn_flag ovpn_addr wf_flag wf_addr bt_flag bt_addr fw_flag fw_addr reboot_flag ip 1 ac:84:... 10:25 Office pass123 1 https:// 1 https:// 1 https:// 0 - 0 192.168.5.244 ``` **Почему важно:** - Быстрый просмотр состояния всех устройств - Сравнение конфигов разных устройств - Видно кто онлайн (last seen), кто офлайн **Решение для нового проекта:** ⚠️ ЧАСТИЧНО - компактная таблица с основным, детали в модальном окне --- #### C. Быстрое редактирование прямо в таблице (inline editing) **Что делает:** - Кликнул на ячейку → она превратилась в input - Изменил значение → нажал "Ok" → сохранено - Не нужно открывать формы, модальные окна - Изменение 1 поля = 2 клика (клик на ячейку + клик Ok) **Use case:** - Админ видит что у device #5 неправильный wf_ssid - Кликает на ячейку - Меняет "Office" на "Office_Guest" - Клик Ok → сохранено - Время: 5 секунд **Почему опасно:** - Случайный клик → случайное изменение - Нет подтверждения - Нет валидации - Нет истории кто и что изменил **Решение для нового проекта:** ❌ НЕ ТАЩИМ - слишком опасно, заменяем модальными окнами с подтверждением --- #### D. Сортировка по любой колонке **Что делает:** - Клик на заголовок колонки → сортировка по возрастанию - Еще клик → сортировка по убыванию - Работает для всех колонок (кроме действий) **Use cases:** - Отсортировать по `time` → увидеть кто давно не подключался - Отсортировать по `MAC` → найти все устройства одного производителя - Отсортировать по `ip` → сгруппировать по подсетям **Решение для нового проекта:** ✅ ТАЩИМ - сортировка обязательно --- ### 2. Clients Panel - Управление клиентами (организациями) #### A. Список клиентов с их устройствами **Что показывает:** ``` # Логин Пароль Устройства Оплачено до Информация Оплата 1 shop@example.com 12345 ac:84:c6:42:17:90 2025-05-01 ИП Иванов, +79001234567 Оплачено до мая cc:2d:e0:ca:9f:7e d8:0d:17:5e:07:94 2 mall@example.com 54321 b8:27:eb:c1:46:0e 2025-03-15 ТЦ "Мега", Петров Лимит 1000 ``` **Ключевая фича:** - Сразу видно сколько устройств у клиента - MAC-адреса отображаются вертикально (каждый с новой строки) - Информация о клиенте и оплате в одной строке **Решение для нового проекта:** ✅ ТАЩИМ - список организаций с количеством устройств и статусом --- #### B. Редактирование даты оплаты **Что делает:** - В колонке "Оплачено до" дата является ссылкой - Клик → переход на tilledit.php?id=123 - Отдельная страница с календарем - Выбрать новую дату → сохранить **Решение для нового проекта:** ✅ ТАЩИМ - но через модальное окно, не отдельную страницу --- #### C. Хранение доп. информации о клиенте **Что хранится:** - Юридическое лицо / ИП - Контактное лицо - Телефон - Email - Адрес - Комментарии **Формат:** Текстовое поле (textarea), админ пишет произвольный текст **Решение для нового проекта:** ✅ ТАЩИМ - но структурированно (отдельные поля, не текст) --- ### 3. Add Device Panel - Создание клиента с устройствами #### A. Одновременное создание клиента и привязка устройств **Workflow:** ``` 1. Админ вводит: - Логин клиента (email) - Пароль - Список MAC-адресов устройств (textarea, через ;) Пример: d8:0d:17:5e:07:94;ac:84:c6:42:17:90;cc:2d:e0:ca:9f:7e; - Информация о клиенте (textarea) - Оплата (checkbox: оплачен / лимит 1000) 2. Система создает: - Запись в таблице `users` (клиент) - Записи в таблице `user_devices` (связь клиент-устройство) - Автоматически все MAC из списка привязываются к клиенту ``` **Почему удобно:** - Не нужно сначала создать клиента, потом добавлять устройства по одному - Массовое добавление устройств (скопировал список MAC → вставил → готово) **Решение для нового проекта:** ✅ ТАЩИМ - форма создания организации с возможностью сразу добавить несколько устройств --- #### B. Простой контроль оплаты/лимитов **Логика:** - Checkbox "Оплачен" - Если НЕ оплачен → устанавливается лимит 1000 (детекций/событий) - Поле "Оплачено до" (дата) - Поле "Оплата" (текст статуса) **Use case:** - Клиент на пробном периоде → checkbox снят → лимит 1000 - Клиент оплатил → checkbox включен → безлимит - Админ видит когда заканчивается период → продлевает **Решение для нового проекта:** ⚠️ ЧАСТИЧНО - заменяем на статусы организации: `pending`, `active`, `suspended` --- ## ЧТО ТАЩИМ В НОВЫЙ ПРОЕКТ ### Обязательные фичи (Must Have) #### 1. ✅ Универсальный поиск **Где:** - Devices panel - Organizations panel - Users panel **Реализация:** ``` Поисковое поле → backend ищет по полям: - Devices: MAC, simple_id, organization.name, organization.owner_email, IP - Organizations: name, contact_email, owner.email - Users: email, full_name, organization.name ``` **Фичи:** - Debounce 300ms (не слать запрос на каждый символ) - Минимум 2 символа для поиска - Подсветка найденного (опционально) - Placeholder с примерами: "Поиск по MAC, организации, владельцу..." --- #### 2. ✅ Компактная таблица с сортировкой **Devices:** ``` Simple ID | MAC Address | Organization | Status | Last Seen | Actions #1 | ac:84:c6:... | Shop LLC | online | 2 min ago | ✏️ Edit #2 | cc:2d:e0:... | Mall Inc | offline | 5 days ago | ✏️ Edit ``` **⚠️ ВАЖНО: Кнопка Delete НЕ в таблице!** - В колонке Actions только кнопка "Edit" - Кнопка "Delete" спрятана ВНУТРИ модального окна редактирования - В секции "Danger Zone" внизу модального окна - Требует двойного подтверждения **Почему:** - Случайный клик на Delete в таблице = катастрофа - Удаление должно быть обдуманным действием - Сначала Edit (открыть модалку) → увидеть детали → Danger Zone → Delete **Сортировка:** - По Simple ID (default: newest first) - По Organization (alphabetically) - По Status (online → offline → error) - По Last Seen (newest → oldest) **Клик на заголовок** → меняет сортировку **Индикатор направления** → стрелка ↑↓ --- #### 3. ✅ Создание организации с устройствами **Форма Create Organization:** **Шаг 1: Информация об организации** ``` Name: ________________ Contact Email: ________ Owner Email: __________ WiFi Enabled: ☐ BLE Enabled: ☐ ``` **Шаг 2: Добавить устройства (опционально)** ``` ⊞ Add Device MAC Address: __________________ [Remove] MAC Address: __________________ [Remove] MAC Address: __________________ [Remove] [+ Add Another Device] ``` **Или массовое добавление:** ``` ☐ Bulk import from list Paste MAC addresses (one per line or separated by ; or ,): ┌─────────────────────────────────────┐ │ ac:84:c6:42:17:90 │ │ cc:2d:e0:ca:9f:7e;d8:0d:17:5e:07:94│ │ b8:27:eb:c1:46:0e │ └─────────────────────────────────────┘ [Parse & Add] → "Found 4 MAC addresses" ``` **Кнопки:** ``` [Create Organization] [Cancel] ``` **После создания:** - Organization создана - Devices добавлены и автоматически привязаны - Owner получает email с доступом --- #### 4. ✅ Статусы и индикаторы **Device status badges:** ```css .status-online → зеленый (#10b981) .status-offline → серый (#6b7280) .status-error → красный (#ef4444) ``` **Last seen human-readable:** ``` 2 minutes ago 5 hours ago 3 days ago Never (для новых устройств) ``` **Organization status:** ``` pending → желтый (ожидает активации superadmin) active → зеленый (работает) suspended → красный (заблокирована) ``` --- #### 5. ✅ Модальные окна для редактирования **Вместо inline editing:** **Edit Device Modal:** ``` ┌─────────────────────────────────────┐ │ Edit Device #5 │ │ │ │ MAC Address: ac:84:c6:42:17:90 │ │ (read-only, не редактируется) │ │ │ │ Organization: [Dropdown ▼] │ │ → Shop LLC │ │ → Mall Inc │ │ → Not assigned │ │ │ │ Status: [Dropdown ▼] │ │ → Online │ │ → Offline │ │ → Error │ │ │ │ Config (JSON): │ │ ┌─────────────────────────┐ │ │ │ { │ │ │ │ "wifi_enabled": true │ │ │ │ } │ │ │ └─────────────────────────┘ │ │ │ │ [Save Changes] [Cancel] │ │ │ │ ───────────────────────────────────│ │ Danger Zone: │ │ [🗑 Delete Device] │ │ (требует подтверждения) │ └─────────────────────────────────────┘ ``` **Преимущества:** - Валидация перед сохранением - Подтверждение действия - Невозможно случайно изменить - Audit log: кто, когда, что изменил - **Кнопка Delete спрятана в модальном окне** (не в таблице!) --- ### Опциональные фичи (Nice to Have) #### 6. ⚠️ Bulk actions (массовые действия) **Что:** - Checkbox в каждой строке таблицы - Checkbox "Select All" в заголовке - Панель массовых действий появляется при выборе **Пример:** ``` ☑ Select All (5 selected) [Change Organization ▼] [Delete Selected] [Export CSV] ``` **Use cases:** - Выбрать 10 устройств → переназначить в другую организацию - Выбрать 5 офлайн устройств → удалить - Выбрать все → экспорт в Excel --- #### 7. ⚠️ Фильтры по статусу (chips) **Вместо dropdown:** ``` Status: [All] [Online] [Offline] [Error] ───── (123) (45) (2) ``` **Клик на chip** → фильтрует таблицу **Счетчик** → показывает количество устройств в каждом статусе --- #### 8. ⚠️ Экспорт в CSV/Excel **Кнопка:** "Export to Excel" **Что экспортирует:** - Текущий отфильтрованный список (с учетом поиска) - Все колонки таблицы - Формат: .xlsx (Excel) или .csv **Use case:** - Админ хочет отчет по всем устройствам организации "Shop LLC" - Поиск "Shop LLC" → 10 devices найдено - Export → получает Excel с 10 строками --- ## ЧТО НЕ ТАЩИМ ### ❌ 1. Inline editing **Почему НЕ тащим:** - Опасно (случайные изменения) - Нет валидации - Нет подтверждения - Нет audit trail **Замена:** Модальные окна --- ### ❌ 2. OpenVPN туннели **Почему НЕ тащим:** - По ТЗ: "Сейчас у нас VPN не будет" - Убираем: - `ovpn_flag` - `ovpn_addr` - VPN страницу --- ### ❌ 3. Множественные флаги и URL в БД **Legacy имело:** ```sql wf_flag tinyint(1) wf_addr varchar(60) -- "https://beacon.e-bash.ru/wifi_receiver.php" bt_flag tinyint(1) bt_addr varchar(60) -- "https://beacon.e-bash.ru/newfilebeacon.php" fw_flag tinyint(1) fw_addr varchar(60) -- "https://beacon.e-bash.ru/fw_update.php?mac=" ``` **Проблемы:** - Хардкод URL в БД - Для изменения URL нужно обновить все записи - Нет версионирования API **Новый подход:** - Модульность на уровне Organization: `wifi_enabled`, `ble_enabled` - Единые API endpoints (не хардкод в БД): - `/api/v1/device/config` - получить конфиг - `/api/v1/device/wifi-events` - отправить WiFi данные - `/api/v1/device/ble-events` - отправить BLE данные - Device config как JSONB для гибкости --- ### ⚠️ 4. WiFi credentials - ИЗМЕНЕНИЯ **Legacy:** ```sql wf_client_ssid varchar(60) -- WiFi SSID wf_client_psk varchar(60) -- WiFi пароль (plain text!) ``` **Проблемы Legacy:** - ❌ Пароли в открытом виде (plain text в БД) - ❌ Хранение в таблице `devices` (нужно обновлять каждое устройство) - ✅ НО: админ и клиент могли смотреть и менять пароли **Новый подход:** **Хранение:** - WiFi credentials на уровне **Organization** (не в каждом device) - Шифрование паролей в БД (AES-256 или аналог) - Возможность расшифровки для показа админу/клиенту **UI - Просмотр и редактирование:** **В Organizations Panel (для superadmin/owner/admin):** ``` ┌────────────────────────────────────────┐ │ Organization: Shop LLC │ │ │ │ WiFi Settings: │ │ │ │ SSID: ___________________ │ │ Shop_Guest_WiFi │ │ │ │ Password: ●●●●●●●●●● [👁 Show] │ │ (click Show → myPassword123) │ │ │ │ [Save Changes] │ └────────────────────────────────────────┘ ``` **Кнопка "Show Password":** - По умолчанию: `●●●●●●●●●●` (скрыт) - Клик "Show" → показывает plain text: `myPassword123` - Клик "Hide" → снова скрывает **Права доступа:** - ✅ **Superadmin** - может смотреть и менять любые credentials - ✅ **Owner** - может смотреть и менять credentials своей организации - ✅ **Admin** - может смотреть и менять credentials своей организации - ❌ **Manager/Operator/Viewer** - НЕ могут смотреть пароли (только SSID) **API Endpoints:** ```python # GET /api/v1/client/organization/wifi-credentials # Permissions: owner, admin { "ssid": "Shop_Guest_WiFi", "password": "myPassword123" # Расшифрованный пароль } # PATCH /api/v1/client/organization/wifi-credentials # Permissions: owner, admin { "ssid": "Shop_Guest_WiFi_New", "password": "newPassword456" } ``` **Backend шифрование:** ```python from cryptography.fernet import Fernet class Organization(Base): # ... wifi_ssid = Column(String(100)) wifi_password_encrypted = Column(String(500)) # Зашифрованный пароль @property def wifi_password(self): """Расшифровать пароль для показа админу""" if not self.wifi_password_encrypted: return None return decrypt_password(self.wifi_password_encrypted) @wifi_password.setter def wifi_password(self, plain_password): """Зашифровать пароль перед сохранением""" self.wifi_password_encrypted = encrypt_password(plain_password) ``` **Device получает через API:** - Device запрашивает `/api/v1/device/config?mac=...` - Backend проверяет `organization_id` устройства - Возвращает расшифрованные WiFi credentials - Device подключается к WiFi **Преимущества:** - ✅ Пароли зашифрованы в БД (безопасно) - ✅ Админ/клиент могут смотреть и менять (удобно) - ✅ Централизованное хранение на уровне Organization - ✅ При смене пароля обновляется одна запись (не все devices) - ✅ Device получает актуальные credentials через API --- ### ❌ 5. Текстовые связи через разделители **Legacy:** ```sql device text NOT NULL -- "b8:27:eb:c1:46:0e;cc:2d:e0:ca:9f:7e;d8:0d:17:5e:07:94;" ``` **Проблемы:** - Невозможно сделать JOIN - Невозможно сделать foreign key constraint - Невозможно индексировать - Парсинг строки каждый раз **Новый подход:** - Нормализованная БД - Таблица `devices` с `organization_id` (foreign key) - Один device = одна строка --- ### ❌ 6. Пароли в открытом виде **Legacy:** ```sql password text NOT NULL -- "12345" (plain text) ``` **Новый подход:** - `hashed_password` varchar(255) - bcrypt с salt - Минимум 8 символов, complexity requirements --- ### ❌ 7. Отсутствие аутентификации в админке **Legacy:** - Нет проверки кто заходит в админку - Любой кто знает URL может зайти **Новый подход:** - JWT аутентификация - Role-based access control - Session management - Audit logging --- ## ПРИОРИТЕТЫ РЕАЛИЗАЦИИ ### ✅ Уже есть (MVP) 1. CRUD устройств с модальными окнами 2. CRUD организаций 3. CRUD пользователей 4. JWT аутентификация 5. Multi-tenant изоляция 6. RBAC (6 ролей) 7. Базовая таблица с сортировкой ### 🔨 TODO Шаг 1: Поиск (приоритет HIGH) **Backend:** ```python @router.get("/devices") async def get_devices( search: Optional[str] = None, organization_id: Optional[int] = None, status: Optional[str] = None, sort_by: str = "simple_id", sort_order: str = "desc", offset: int = 0, limit: int = 50 ): query = select(Device) # Search filter if search: query = query.join(Organization).where( or_( Device.mac_address.ilike(f"%{search}%"), cast(Device.simple_id, String).ilike(f"%{search}%"), Organization.name.ilike(f"%{search}%"), Organization.contact_email.ilike(f"%{search}%") ) ) # Status filter if status: query = query.where(Device.status == status) # Sorting if sort_order == "asc": query = query.order_by(asc(getattr(Device, sort_by))) else: query = query.order_by(desc(getattr(Device, sort_by))) total = await db.scalar(select(func.count()).select_from(query.subquery())) devices = await db.scalars(query.offset(offset).limit(limit)) return {"devices": devices.all(), "total": total} ``` **Frontend:** ```vue ``` ### 🔨 TODO Шаг 2: Bulk import устройств (приоритет MEDIUM) **Форма Create Organization:** Добавить секцию: ``` ☐ Bulk add devices Paste MAC addresses: ┌────────────────────────────┐ │ ac:84:c6:42:17:90 │ │ cc:2d:e0:ca:9f:7e │ │ d8:0d:17:5e:07:94 │ └────────────────────────────┘ Separator: [; or , or newline ▼] [Parse] → "✓ Found 3 valid MAC addresses" [Create Organization with 3 devices] ``` **Backend:** ```python @router.post("/organizations") async def create_organization( data: OrganizationCreateSchema, devices: Optional[List[str]] = None # List of MAC addresses ): # Create organization org = Organization(**data.dict()) db.add(org) await db.flush() # Create devices if provided if devices: for mac in devices: device = Device( mac_address=mac, organization_id=org.id, simple_id=await get_next_simple_id() ) db.add(device) await db.commit() return org ``` ### 🔨 TODO Шаг 3: Фильтры по статусу (приоритет LOW) **UI:** ```vue
``` **Backend:** ```python @router.get("/devices/counts") async def get_device_counts(): counts = await db.execute( select( Device.status, func.count(Device.id).label('count') ).group_by(Device.status) ) return {row.status: row.count for row in counts} ``` --- ## ИТОГО: Сравнение Legacy → New | Фича | Legacy | New Project | Status | |------|--------|-------------|--------| | **Универсальный поиск** | ✅ DataTables | ✅ Backend SQL | 🔨 TODO | | **Компактная таблица** | ✅ Все поля видны | ✅ Основные поля | ✅ Done | | **Сортировка** | ✅ По всем колонкам | ✅ По ключевым | ✅ Done | | **Inline editing** | ✅ jEditable | ❌ Убрали | - | | **Модальные окна** | ❌ Не было | ✅ Для редактирования | ✅ Done | | **Bulk add devices** | ✅ Textarea с ";" | ✅ Парсинг списка | 🔨 TODO | | **Multi-tenant** | ❌ Все в одной куче | ✅ Изоляция | ✅ Done | | **RBAC** | ❌ Нет ролей | ✅ 6 ролей | ✅ Done | | **Audit logs** | ❌ Нет | ✅ Полное логирование | ✅ Done | | **JWT auth** | ❌ Нет | ✅ Access + refresh | ✅ Done | | **Статусы** | ❌ Только last seen | ✅ online/offline/error | ✅ Done | | **Simple ID** | ❌ Только MAC | ✅ #1, #2, #3 | ✅ Done | | **OpenVPN** | ✅ Было | ❌ Убрали | - | | **WiFi credentials** | ✅ Plain text в devices | ✅ Encrypted в org + UI | 🔨 TODO | | **Пароли юзеров** | ❌ Plain text | ✅ Bcrypt hash | ✅ Done | --- ## ВЫВОДЫ ### Что сохранили (essence, не реализацию): 1. ✅ **Универсальный поиск** - самая полезная фича для быстрой работы 2. ✅ **Компактное отображение** - вся информация на виду 3. ✅ **Быстрое создание** - организация + устройства одним действием 4. ✅ **Сортировка** - удобный просмотр больших списков ### Что выбросили: 1. ❌ **Inline editing** - опасно, заменили на модальные окна 2. ❌ **OpenVPN** - не нужно по ТЗ 3. ❌ **Хардкод в БД** - заменили на JSONB config + API endpoints 4. ❌ **Текстовые связи** - заменили на нормализованную БД 5. ❌ **Plain text user passwords** - заменили на bcrypt 6. ❌ **Отсутствие auth** - добавили JWT + RBAC ### Что улучшили: 1. ✨ **Безопасность:** JWT, RBAC, bcrypt, SQL injection защита 2. ✨ **Multi-tenant:** изоляция данных по организациям 3. ✨ **Audit trail:** кто, когда, что сделал 4. ✨ **Модальные окна:** валидация, подтверждение, UX 5. ✨ **Simple ID:** удобные номера вместо MAC 6. ✨ **Статусы:** online/offline/error с индикаторами 7. ✨ **i18n:** RU/EN поддержка ### Next steps: 1. 🔨 Реализовать универсальный поиск (HIGH priority) 2. 🔨 WiFi credentials UI с шифрованием (HIGH priority) 3. 🔨 Bulk import устройств (MEDIUM priority) 4. 🔨 Фильтры по статусу (LOW priority) 5. 🔨 Export to CSV/Excel (LOW priority)