Составить план модернизации интерфей

This commit is contained in:
Mikhail Chusavitin
2026-02-04 09:20:30 +03:00
parent 241e4e3605
commit 8e99c36888
5 changed files with 217 additions and 164 deletions

253
CLAUDE.md
View File

@@ -1,199 +1,124 @@
# LOGPile - Инструкции для Claude Code
## Описание проекта
## Что это за проект
Приложение для анализа диагностической информации с BMC серверов (IPMI).
Представляет собой standalone Go-бинарник со встроенным веб-интерфейсом.
LOGPile - standalone Go-приложение для анализа BMC/IPMI диагностических архивов с веб-интерфейсом.
Приложение запускает локальный HTTP-сервер, парсит архив, автоматически выбирает подходящий parser по vendor и показывает результат в UI + экспортирует данные.
### Функциональность
## Актуальная архитектура
**Входные данные:**
- Архив (tar.gz/zip) с диагностическими данными IPMI сервера
- Язык: Go 1.22+
- HTTP: стандартный `net/http` + `http.ServeMux`
- UI: embedded (`//go:embed`) HTML/CSS/Vanilla JS
- Бинарник: один executable, без внешних зависимостей на runtime
- Порт по умолчанию: `8082` (а не 8080)
**Обработка:**
- Парсинг System Event Log (SEL) - журнал событий IPMI
- Парсинг FRU (Field Replaceable Unit) - серийные номера компонентов
- Парсинг конфигурации сервера (CPU, RAM, диски, и т.д.)
**Выходные данные:**
- Веб-интерфейс с человекочитаемой информацией
- Экспорт логов в TXT/JSON
- Экспорт конфигурации в JSON
- Экспорт серийных номеров в CSV
## Архитектура
- **Тип:** Standalone бинарник с embedded веб-сервером
- **Язык:** Go
- **UI:** Embedded HTML + CSS + Vanilla JS
- **Порт:** localhost:8080 (по умолчанию)
## Структура проекта
## Реальная структура репозитория
```
logpile/
├── cmd/logpile/main.go # Точка входа
├── cmd/logpile/main.go
├── internal/
│ ├── parser/ # Парсинг архивов и IPMI данных
│ ├── models/ # Модели данных
│ ├── analyzer/ # Логика анализа
│ ├── exporter/ # Экспорт данных
│ └── server/ # HTTP сервер и handlers
├── web/ # Embedded веб-интерфейс
├── static/ # CSS, JS, изображения
└── templates/ # HTML шаблоны
├── testdata/ # Примеры архивов для тестов
├── go.mod
│ ├── analyzer/
│ ├── exporter/
│ ├── models/
│ ├── parser/
│ └── vendors/
│ │ ├── generic/
│ ├── inspur/
│ ├── nvidia/
├── nvidia_bug_report/
│ │ └── supermicro/
│ └── server/
├── web/
│ ├── static/
│ └── templates/
├── Makefile
└── README.md
└── go.mod
```
## Технический стек
### Backend
- Go 1.22+
- Стандартная библиотека (net/http, archive/tar, compress/gzip)
- embed для встраивания веб-ресурсов
### Frontend
- Vanilla JavaScript
- CSS (встроенный в бинарник)
- Без сборщиков - всё embedded в бинарник
### Парсинг IPMI
- SEL формат: текстовый вывод `ipmitool sel list` или бинарный
- FRU формат: вывод `ipmitool fru print`
- Конфигурация: различные текстовые файлы из архива
## Реализованные функции
### 1. Базовая структура
- [x] Создана структура директорий
- [x] go.mod инициализирован
- [x] Makefile создан
### 2. Парсер архивов
- [x] Распаковка tar.gz
- [x] Распаковка zip
- [x] Определение типов файлов внутри архива
### 3. Парсеры IPMI данных
- [x] SEL parser (System Event Log)
- [x] FRU parser (серийные номера)
- [x] Config parser (конфигурация сервера)
- [x] Поддержка нескольких производителей (Supermicro, Inspur, Nvidia, etc.)
### 4. Модели данных
- [x] Event (события из SEL)
- [x] Hardware (конфигурация)
- [x] SerialNumber (серийники компонентов)
### 5. Веб-сервер
- [x] HTTP сервер с embedded файлами
- [x] Upload handler для архивов
- [x] API endpoints для получения данных
- [x] Handlers для экспорта
### 6. Веб-интерфейс
- [x] Главная страница с upload формой
- [x] Отображение событий (timeline/таблица)
- [x] Отображение конфигурации
- [x] Таблица серийных номеров
- [x] Кнопки экспорта
### 7. Экспортеры
- [x] CSV экспорт (серийники)
- [x] JSON экспорт (конфиг, события)
- [x] TXT отчет (логи)
### 8. Тестирование и сборка
- [x] Unit тесты для парсеров
- [x] Интеграционные тесты
- [x] Cross-platform сборка (Linux, Windows, Mac)
## Примеры использования
## CLI и запуск (актуально)
```bash
# Сборка
make build
# Запуск веб-сервера
./bin/logpile serve
# Запуск (авто-открытие браузера включено)
./bin/logpile
# Открыть в браузере
open http://localhost:8080
# Явный порт
./bin/logpile --port 8082
# С указанием порта
./bin/logpile serve --port 9000
# Не открывать браузер автоматически
./bin/logpile --no-browser
# С предзагрузкой файла
./bin/logpile serve --file /path/to/bmc-archive.tar.gz
# Версия
./bin/logpile --version
```
## Формат данных IPMI
Важно: сейчас **нет** subcommand `serve`, запуск идёт напрямую через флаги.
### SEL (System Event Log)
```
SEL Record ID : 0001
Record Type : 02
Timestamp : 01/15/2025 14:23:45
Generator ID : 0020
EvM Revision : 04
Sensor Type : Temperature
Sensor Number : 01
Event Type : Threshold
Event Direction : Assertion Event
Event Data : 010000
Description : Upper Critical - going high
```
## Основной runtime-flow
### FRU (Field Replaceable Unit)
```
FRU Device Description : Builtin FRU Device (ID 0)
Board Mfg Date : Mon Jan 1 00:00:00 1996
Board Mfg : Supermicro
Board Product : X11DPH-T
Board Serial : WM194S001234
Board Part Number : X11DPH-TQ
```
1. `main.go` регистрирует embedded web FS и запускает сервер.
2. `POST /api/upload` принимает архив и передаёт его в `parser.BMCParser`.
3. `DetectFormat()` выбирает parser с максимальным confidence.
4. Результат сохраняется в памяти (`Server.result`) и отдаётся через API.
5. UI строит вкладки: конфигурация, прошивки, сенсоры, серийники, события.
## API Endpoints
## Поддерживаемые parser modules
- `supermicro` - Supermicro parser
- `inspur` - Inspur/Kaytus parser
- `nvidia` - NVIDIA Field Diagnostics parser
- `nvidia_bug_report` - parser для `nvidia-bug-report.sh`
- `generic` - fallback parser
Реестр parser-ов: `internal/parser/registry.go`, подключение модулей: `internal/parser/vendors/vendors.go`.
## API (фактически в коде)
```
POST /api/upload # Загрузить архив
GET /api/status # Получить статус парсинга
GET /api/parsers # Получить список доступных парсеров
GET /api/events # Получить список событий
GET /api/sensors # Получить показания сенсоров
GET /api/config # Получить конфигурацию
GET /api/serials # Получить серийные номера
GET /api/firmware # Получить версии прошивок
GET /api/export/csv # Экспорт в CSV
GET /api/export/json # Экспорт в JSON
GET /api/export/txt # Экспорт текстового отчета
DELETE /api/clear # Очистить загруженные данные
POST /api/shutdown # Завершить работу приложения
POST /api/upload
GET /api/status
GET /api/parsers
GET /api/events
GET /api/sensors
GET /api/config
GET /api/serials
GET /api/firmware
GET /api/export/csv
GET /api/export/json
GET /api/export/txt
DELETE /api/clear
POST /api/shutdown
```
## Поддерживаемые производители
## Форматы данных и экспорт
- Supermicro
- Inspur/Kaytus
- Nvidia
- Generic (fallback)
- `AnalysisResult` агрегирует: events, sensors, FRU, hardware.
- Экспорт реализован в `internal/exporter/exporter.go`:
- CSV: серийные номера компонентов
- JSON: полный `AnalysisResult`
- TXT: человекочитаемый отчёт
## Следующие шаги
## Важные текущие ограничения (чтобы не ошибаться в задачах)
1. Добавление поддержки других производителей BMC (Dell iDRAC, HP iLO, Lenovo XCC)
2. Расширение функционала экспорта данных
3. Добавление фильтрации и сортировки данных в UI
4. Улучшение производительности парсинга больших архивов
5. Добавление поддержки других форматов IPMI данных
- Upload через `/api/upload` использует `ParseFromReader()`, где сейчас поддержаны `.tar`, `.tar.gz`, `.tgz`.
- Код распаковки `.zip` есть, но в текущем upload-пути `zip` не обрабатывается.
- Флаг `--file` присутствует в CLI-конфиге, но preload в `Server.Run()` сейчас не выполняется.
- Данные хранятся только в памяти процесса; перезапуск очищает состояние.
## Примечания
## Практические рекомендации для доработок
- Все файлы веб-интерфейса должны быть embedded в бинарник через `//go:embed`
- Приоритет на простоту и минимум зависимостей
- Безопасность: валидация загружаемых архивов (размер, типы файлов)
- UI должен быть простым и функциональным, не перегруженным
- Поддержка русского языка в интерфейсе
- Если меняется parser-логика, обновляй `Version()` соответствующего parser-модуля.
- Новые vendor-парсеры регистрируй через import в `internal/parser/vendors/vendors.go`.
- Для API/контрактов проверяй согласованность `handlers.go` и `web/static/js/app.js`.
- Для UI-изменений не забывай, что ассеты embedded через `web/embed.go`.
## Приоритетные следующие шаги
1. Довести поддержку `zip` в upload path (`ParseFromReader`).
2. Реализовать preload из `--file`.
3. Добавить/актуализировать автотесты для parser и HTTP handlers.
4. Расширить vendor coverage (Dell/HPE/Lenovo) по реальным дампам.

BIN
logpile

Binary file not shown.

32
quick_test.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
"log"
"git.mchus.pro/mchus/logpile/internal/parser"
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors"
)
func main() {
p := parser.NewBMCParser()
fmt.Println("Testing archive parsing...")
if err := p.ParseArchive("example/A514359X5A07900_logs-20260122-074208.tar"); err != nil {
log.Fatalf("ERROR: %v", err)
}
fmt.Println("✓ Archive parsed successfully!")
fmt.Printf("✓ Detected vendor: %s\n", p.DetectedVendor())
result := p.Result()
fmt.Printf("✓ GPUs found: %d\n", len(result.Hardware.GPUs))
fmt.Printf("✓ Events found: %d\n", len(result.Events))
fmt.Printf("✓ PCIe Devices found: %d\n", len(result.Hardware.PCIeDevices))
fmt.Println("\nBoard Info:")
fmt.Printf(" Manufacturer: %s\n", result.Hardware.BoardInfo.Manufacturer)
fmt.Printf(" Product Name: %s\n", result.Hardware.BoardInfo.ProductName)
fmt.Printf(" Serial Number: %s\n", result.Hardware.BoardInfo.SerialNumber)
fmt.Printf(" Part Number: %s\n", result.Hardware.BoardInfo.PartNumber)
}

BIN
test_nvidia_full Executable file

Binary file not shown.

96
test_nvidia_full.go Normal file
View File

@@ -0,0 +1,96 @@
package main
import (
"fmt"
"log"
"git.mchus.pro/mchus/logpile/internal/parser"
_ "git.mchus.pro/mchus/logpile/internal/parser/vendors"
)
func main() {
p := parser.NewBMCParser()
fmt.Println("Testing NVIDIA Bug Report parser (full)...")
if err := p.ParseArchive("/Users/mchusavitin/Downloads/nvidia-bug-report-2KD501412.log.gz"); err != nil {
log.Fatalf("ERROR: %v", err)
}
fmt.Println("✓ Archive parsed successfully!")
fmt.Printf("✓ Detected vendor: %s\n", p.DetectedVendor())
result := p.Result()
fmt.Printf("✓ CPUs: %d\n", len(result.Hardware.CPUs))
fmt.Printf("✓ Memory: %d modules\n", len(result.Hardware.Memory))
fmt.Printf("✓ Power Supplies: %d\n", len(result.Hardware.PowerSupply))
fmt.Printf("✓ GPUs: %d\n", len(result.Hardware.GPUs))
fmt.Printf("✓ Network Adapters: %d\n", len(result.Hardware.NetworkAdapters))
fmt.Println("\nSystem Information:")
if result.Hardware.BoardInfo.SerialNumber != "" {
fmt.Printf(" Serial Number: %s\n", result.Hardware.BoardInfo.SerialNumber)
}
if result.Hardware.BoardInfo.UUID != "" {
fmt.Printf(" UUID: %s\n", result.Hardware.BoardInfo.UUID)
}
if result.Hardware.BoardInfo.Manufacturer != "" {
fmt.Printf(" Manufacturer: %s\n", result.Hardware.BoardInfo.Manufacturer)
}
if result.Hardware.BoardInfo.ProductName != "" {
fmt.Printf(" Product: %s\n", result.Hardware.BoardInfo.ProductName)
}
if result.Hardware.BoardInfo.Version != "" {
fmt.Printf(" Version: %s\n", result.Hardware.BoardInfo.Version)
}
fmt.Println("\nCPU Information:")
for _, cpu := range result.Hardware.CPUs {
fmt.Printf(" Socket %d: %s\n", cpu.Socket, cpu.Model)
fmt.Printf(" S/N: %s, Cores: %d, Threads: %d\n", cpu.SerialNumber, cpu.Cores, cpu.Threads)
}
fmt.Println("\nPower Supplies:")
for _, psu := range result.Hardware.PowerSupply {
fmt.Printf(" %s: %s (%s)\n", psu.Slot, psu.Model, psu.Vendor)
fmt.Printf(" S/N: %s\n", psu.SerialNumber)
fmt.Printf(" Power: %d W, Revision: %s\n", psu.WattageW, psu.Firmware)
fmt.Printf(" Status: %s\n", psu.Status)
}
totalMemGB := 0
for _, mem := range result.Hardware.Memory {
totalMemGB += mem.SizeMB / 1024
}
fmt.Printf("\nMemory: %d modules, %d GB total\n", len(result.Hardware.Memory), totalMemGB)
fmt.Printf("\nNetwork Adapters: %d devices\n", len(result.Hardware.NetworkAdapters))
for _, nic := range result.Hardware.NetworkAdapters {
fmt.Printf(" %s: %s\n", nic.Location, nic.Model)
if nic.Slot != "" {
fmt.Printf(" Slot: %s\n", nic.Slot)
}
if nic.PartNumber != "" {
fmt.Printf(" P/N: %s\n", nic.PartNumber)
}
if nic.SerialNumber != "" {
fmt.Printf(" S/N: %s\n", nic.SerialNumber)
}
if nic.PortCount > 0 {
fmt.Printf(" Ports: %d x %s\n", nic.PortCount, nic.PortType)
}
}
fmt.Printf("\nGPUs: %d devices\n", len(result.Hardware.GPUs))
for _, gpu := range result.Hardware.GPUs {
fmt.Printf(" %s: %s\n", gpu.BDF, gpu.Model)
if gpu.UUID != "" {
fmt.Printf(" UUID: %s\n", gpu.UUID)
}
if gpu.VideoBIOS != "" {
fmt.Printf(" Video BIOS: %s\n", gpu.VideoBIOS)
}
if gpu.IRQ > 0 {
fmt.Printf(" IRQ: %d\n", gpu.IRQ)
}
}
}