415 lines
23 KiB
Markdown
415 lines
23 KiB
Markdown
# 🔍 Анализ логики настроек ESP - Детальная проверка
|
||
|
||
## ✅ Проверенные компоненты
|
||
|
||
### 1. **Frontend (Client)**
|
||
|
||
#### 📄 [client/src/composables/config/useConfigESP.js](../client/src/composables/config/useConfigESP.js)
|
||
|
||
**Статус:** ✅ Логика корректна
|
||
|
||
**Проверенные аспекты:**
|
||
- ✅ Валидация параметров перед отправкой (строки 93-113)
|
||
- ✅ Загрузка настроек через `cfg-esp-get` (строки 118-143)
|
||
- ✅ Сохранение параметра через `cfg-esp` (строки 148-190)
|
||
- ✅ Откат значения при ошибке (строки 176, 183)
|
||
- ✅ Обработка ответа `error` от сервера (строки 174-177)
|
||
- ✅ Отслеживание изменений через `hasChanges` (строки 79-88)
|
||
|
||
**Ключевые моменты:**
|
||
```javascript
|
||
// Валидация
|
||
const validation = validateParam(paramName, config.value[paramName])
|
||
if (!validation.valid) {
|
||
error.value = validation.error
|
||
return { success: false, error: validation.error }
|
||
}
|
||
|
||
// Откат при ошибке
|
||
if (response.do === 'error') {
|
||
config.value[paramName] = originalConfig.value[paramName]
|
||
throw new Error(response.message || 'Ошибка сохранения')
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 📄 [client/src/pages/ConfigPage.vue](../client/src/pages/ConfigPage.vue)
|
||
|
||
**Статус:** ✅ Логика корректна (исправлена)
|
||
|
||
**Проверенные аспекты:**
|
||
- ✅ Проверка прав доступа: `user.taccess === 'technics'` или `'admins'` (строка 163)
|
||
- ✅ Загрузка настроек при монтировании (строки 188-192)
|
||
- ✅ Визуальная индикация изменений
|
||
- ✅ Предупреждение при выходе с несохранёнными изменениями (строки 178-186)
|
||
|
||
**Исправления:**
|
||
```javascript
|
||
// БЫЛО (неправильно):
|
||
return role === 'technic' || role === 'admin' || user.group === 3
|
||
|
||
// СТАЛО (правильно):
|
||
return user.taccess === 'technics' || user.taccess === 'admins' || user.group === 3
|
||
```
|
||
|
||
---
|
||
|
||
#### 📄 [client/src/composables/useWebSocket.js](../client/src/composables/useWebSocket.js)
|
||
|
||
**Статус:** ✅ Логика корректна (добавлена обработка)
|
||
|
||
**Добавленная обработка (строки 71-80):**
|
||
```javascript
|
||
// Обрабатываем ответы настроек ESP и ошибки через promise
|
||
if (data.do === 'cfg-esp-get' || data.do === 'cfg-esp' || data.do === 'error') {
|
||
const promiseKeys = Object.keys(pendingPromises)
|
||
if (promiseKeys.length > 0) {
|
||
const promiseId = promiseKeys[promiseKeys.length - 1]
|
||
pendingPromises[promiseId].resolve(data)
|
||
delete pendingPromises[promiseId]
|
||
return
|
||
}
|
||
}
|
||
```
|
||
|
||
**Зачем это нужно:**
|
||
- Без этого кода ответы `cfg-esp-get`, `cfg-esp` и `error` не попадали бы в promise
|
||
- Promise висел бы в pending состоянии до timeout
|
||
- UI не получал бы ответ от сервера
|
||
|
||
---
|
||
|
||
### 2. **Backend (Server)**
|
||
|
||
#### 📄 [server/ws.js](../server/ws.js)
|
||
|
||
**Статус:** ✅ Логика корректна (добавлена поддержка mock режима)
|
||
|
||
**Обработчик `cfg-esp-get` (строки 952-972):**
|
||
```javascript
|
||
case "cfg-esp-get":
|
||
try {
|
||
const espConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'data/esp.ini'), 'utf8'));
|
||
sendToClient(wsClient, {
|
||
do: "cfg-esp-get",
|
||
cfg: espConfig
|
||
});
|
||
} catch (error) {
|
||
sendToClient(wsClient, {
|
||
do: "error",
|
||
message: "Не удалось загрузить настройки ESP",
|
||
type: "esp_config_read_error"
|
||
});
|
||
}
|
||
break;
|
||
```
|
||
|
||
**Обработчик `cfg-esp` (строки 974-1038):**
|
||
|
||
**Ключевые исправления:**
|
||
```javascript
|
||
// БЫЛО (неправильно для Windows):
|
||
if (result.success) {
|
||
await fs.writeFile(configPath, JSON.stringify(espConfig, null, 2));
|
||
// ...
|
||
}
|
||
|
||
// СТАЛО (правильно):
|
||
const isMockMode = result.error && result.error.includes('mock mode');
|
||
|
||
if (result.success || isMockMode) {
|
||
await fs.writeFile(configPath, JSON.stringify(espConfig, null, 2));
|
||
// ...
|
||
if (isMockMode) {
|
||
console.log(`[WS CFG-ESP] ⚠️ MOCK РЕЖИМ: Параметр сохранён без отправки на ESP`);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Зачем это нужно:**
|
||
- В Windows режиме разработки ESP не подключена (mock режим)
|
||
- `game.safeWrite()` возвращает `{ success: false, error: 'ESP disconnected (mock mode)' }`
|
||
- Без проверки на `isMockMode` настройки НЕ сохранялись бы в файл
|
||
- Теперь в mock режиме настройки сохраняются, но с предупреждением
|
||
|
||
---
|
||
|
||
## 🔄 Полный поток данных
|
||
|
||
### Сценарий 1: Загрузка настроек
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 1. ConfigPage.vue (onMounted) │
|
||
│ └─> loadConfig() │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 2. useConfigESP.js │
|
||
│ └─> socket.sendMessage({ do: 'cfg-esp-get' }) │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ WebSocket
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 3. server/ws.js:952 │
|
||
│ └─> Читает data/esp.ini │
|
||
│ └─> Отправляет {do:"cfg-esp-get", cfg:{...}} │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ WebSocket
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 4. useWebSocket.js:72 │
|
||
│ └─> Обнаруживает data.do === 'cfg-esp-get' │
|
||
│ └─> Резолвит promise │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 5. useConfigESP.js:128 │
|
||
│ └─> config.value = { ...response.cfg } │
|
||
│ └─> originalConfig.value = { ...response.cfg } │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 6. ConfigPage.vue │
|
||
│ └─> Отображает настройки в UI │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### Сценарий 2: Сохранение параметра (УСПЕХ)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 1. ConfigPage.vue │
|
||
│ └─> Пользователь изменяет hz с 14000 на 15000 │
|
||
│ └─> Кликает "Применить" │
|
||
│ └─> saveParam('hz') │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 2. useConfigESP.js:148 │
|
||
│ └─> Валидация: 9999 ≤ 15000 ≤ 100000 ✅ │
|
||
│ └─> socket.sendMessage({ │
|
||
│ do: 'cfg-esp', │
|
||
│ cfg: 'hz', │
|
||
│ value: 15000 │
|
||
│ }) │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ WebSocket
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 3. server/ws.js:974 │
|
||
│ └─> Читает текущий esp.ini │
|
||
│ └─> Сохраняет previousValue = 14000 │
|
||
│ └─> Обновляет espConfig.hz = 15000 │
|
||
│ └─> game.safeWrite("hz=15000;\n") │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ Serial Port
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 4. game.js:127 (safeWrite) │
|
||
│ └─> Проверяет port.isOpen │
|
||
│ └─> Production: отправляет на ESP │
|
||
│ └─> Mock (Windows): return {success:false, error:...} │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 5. server/ws.js:1000 │
|
||
│ └─> const isMockMode = result.error?.includes('mock') │
|
||
│ └─> if (result.success || isMockMode) │
|
||
│ └─> Сохраняет в esp.ini │
|
||
│ └─> sendToClient({do:"cfg-esp", cfg:"hz", value:15000})│
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ WebSocket
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 6. useWebSocket.js:72 │
|
||
│ └─> Обнаруживает data.do === 'cfg-esp' │
|
||
│ └─> Резолвит promise │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 7. useConfigESP.js:169 │
|
||
│ └─> originalConfig.value.hz = 15000 │
|
||
│ └─> return { success: true } │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 8. ConfigPage.vue │
|
||
│ └─> Кнопка "Применить" исчезает │
|
||
│ └─> Показывается "✓ Сохранено" │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### Сценарий 3: Сохранение параметра (ОШИБКА ESP)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 1-3. Аналогично успешному сценарию │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 4. game.js:127 (safeWrite) │
|
||
│ └─> ESP не отвечает / ошибка отправки │
|
||
│ └─> return { success: false, error: "timeout" } │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 5. server/ws.js:1018 │
|
||
│ └─> result.success === false │
|
||
│ └─> isMockMode === false (реальная ошибка) │
|
||
│ └─> espConfig.hz = previousValue (откат к 14000) │
|
||
│ └─> sendToClient({ │
|
||
│ do: "error", │
|
||
│ message: "Ошибка настройки ESP: timeout", │
|
||
│ type: "esp_config_failed", │
|
||
│ cfg: "hz" │
|
||
│ }) │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓ WebSocket
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 6. useWebSocket.js:72 │
|
||
│ └─> Обнаруживает data.do === 'error' │
|
||
│ └─> Резолвит promise с error │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 7. useConfigESP.js:174 │
|
||
│ └─> response.do === 'error' │
|
||
│ └─> config.value.hz = originalConfig.value.hz (14000) │
|
||
│ └─> throw new Error(response.message) │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 8. useConfigESP.js:181 (catch) │
|
||
│ └─> config.value.hz = originalConfig.value.hz (14000) │
|
||
│ └─> error.value = err.message │
|
||
│ └─> return { success: false, error: err.message } │
|
||
└────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 9. ConfigPage.vue │
|
||
│ └─> Значение откатывается к 14000 │
|
||
│ └─> Показывается ошибка "❌ Не удалось сохранить" │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🛡️ Проверки безопасности
|
||
|
||
### 1. **Валидация на клиенте**
|
||
```javascript
|
||
validateParam(paramName, value) {
|
||
const schema = ESP_CONFIG_SCHEMA[paramName]
|
||
const numValue = Number(value)
|
||
|
||
if (isNaN(numValue)) {
|
||
return { valid: false, error: 'Значение должно быть числом' }
|
||
}
|
||
|
||
if (numValue < schema.min || numValue > schema.max) {
|
||
return { valid: false, error: `Значение должно быть от ${schema.min} до ${schema.max}` }
|
||
}
|
||
|
||
return { valid: true, error: null }
|
||
}
|
||
```
|
||
|
||
### 2. **Проверка прав доступа**
|
||
```javascript
|
||
const hasAccess = computed(() => {
|
||
return user.taccess === 'technics' || user.taccess === 'admins' || user.group === 3
|
||
})
|
||
```
|
||
|
||
### 3. **Откат при ошибках**
|
||
- **Клиент:** Откат `config.value` к `originalConfig.value`
|
||
- **Сервер:** Откат `espConfig` к `previousValue`
|
||
- **Файл:** Сохранение только при успехе
|
||
|
||
---
|
||
|
||
## ⚠️ Найденные и исправленные проблемы
|
||
|
||
### Проблема 1: Обработка WebSocket ответов
|
||
**Проблема:** Ответы `cfg-esp-get`, `cfg-esp`, `error` не обрабатывались в `useWebSocket.js`
|
||
|
||
**Решение:** Добавлена специальная обработка в `onmessage` (строки 71-80)
|
||
|
||
---
|
||
|
||
### Проблема 2: Mock режим Windows
|
||
**Проблема:** В Windows (mock режим) настройки НЕ сохранялись
|
||
|
||
**Решение:** Добавлена проверка `isMockMode` для разрешения сохранения без ESP
|
||
|
||
---
|
||
|
||
### Проблема 3: Проверка прав доступа
|
||
**Проблема:** Неправильные значения ролей (`technic` вместо `technics`)
|
||
|
||
**Решение:** Исправлено на `user.taccess === 'technics'` и `'admins'`
|
||
|
||
---
|
||
|
||
## ✅ Итоговая оценка
|
||
|
||
### Что работает ПРАВИЛЬНО:
|
||
|
||
✅ Загрузка настроек ESP
|
||
✅ Сохранение параметров
|
||
✅ Валидация диапазонов
|
||
✅ Откат при ошибках
|
||
✅ Обработка mock режима (Windows)
|
||
✅ Проверка прав доступа
|
||
✅ Визуальная индикация изменений
|
||
✅ Предупреждение о несохранённых изменениях
|
||
✅ WebSocket promise система
|
||
✅ Детальное логирование
|
||
|
||
---
|
||
|
||
## 🚀 Рекомендации для тестирования
|
||
|
||
### 1. **Тест загрузки настроек**
|
||
```
|
||
1. Открыть /config
|
||
2. Проверить, что отображаются текущие значения из esp.ini
|
||
3. Проверить логи: "[WS CFG-ESP-GET] ✅ Настройки ESP отправлены"
|
||
```
|
||
|
||
### 2. **Тест сохранения (успех)**
|
||
```
|
||
1. Изменить hz на 15000
|
||
2. Нажать "Применить"
|
||
3. Проверить, что кнопка исчезла
|
||
4. Проверить, что esp.ini обновлён
|
||
5. Проверить логи: "[WS CFG-ESP] ✅ Параметр hz успешно сохранён"
|
||
```
|
||
|
||
### 3. **Тест валидации**
|
||
```
|
||
1. Изменить hz на 999999 (вне диапазона)
|
||
2. Нажать "Применить"
|
||
3. Проверить ошибку: "Значение должно быть от 9999 до 100000"
|
||
```
|
||
|
||
### 4. **Тест прав доступа**
|
||
```
|
||
1. Войти как operator
|
||
2. Открыть /config
|
||
3. Проверить сообщение "Нет доступа"
|
||
```
|
||
|
||
### 5. **Тест несохранённых изменений**
|
||
```
|
||
1. Изменить hz (НЕ нажимать "Применить")
|
||
2. Нажать "Назад"
|
||
3. Проверить подтверждение "Есть несохранённые изменения..."
|
||
```
|
||
|
||
---
|
||
|
||
*Анализ завершён: 2025-01-06*
|
||
*Все критические проблемы найдены и исправлены* ✅
|