feat: автосинхронизация компонентов для новых пользователей и Support Bundle

- Воркер теперь запускает SyncComponents при пустой local_components,
  чтобы новый пользователь получил каталог компонентов без ручного действия
- Результат синхронизации компонентов персистируется в app_settings
  (last_component_sync_status/error/attempt_at) по аналогии с прайслистами
- Добавлен эндпоинт GET /api/support-bundle: скачивает ZIP с диагностикой
  (app_info, local_db_stats, db_connection с TCP-пингом, sync_readiness,
  system_metrics с памятью и диском, schema_migrations, app.log)
- Кнопка-иконка в шапке рядом с юзернеймом для скачивания бандла

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 12:50:41 +03:00
parent c951ceb44b
commit 84cab011d3
9 changed files with 294 additions and 0 deletions

View File

@@ -187,8 +187,10 @@ func (h *SyncHandler) SyncComponents(c *gin.Context) {
return
}
now := time.Now()
result, err := h.localDB.SyncComponents(mariaDB)
if err != nil {
_ = h.localDB.SetComponentSyncResult("error", err.Error(), now)
slog.Error("component sync failed", "error", err)
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
@@ -197,6 +199,7 @@ func (h *SyncHandler) SyncComponents(c *gin.Context) {
_ = c.Error(err)
return
}
_ = h.localDB.SetComponentSyncResult("ok", "", now)
c.JSON(http.StatusOK, SyncResultResponse{
Success: true,
@@ -313,8 +316,10 @@ func (h *SyncHandler) SyncAll(c *gin.Context) {
return
}
compNow := time.Now()
compResult, err := h.localDB.SyncComponents(mariaDB)
if err != nil {
_ = h.localDB.SetComponentSyncResult("error", err.Error(), compNow)
slog.Error("component sync failed during full sync", "error", err)
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
@@ -323,6 +328,7 @@ func (h *SyncHandler) SyncAll(c *gin.Context) {
_ = c.Error(err)
return
}
_ = h.localDB.SetComponentSyncResult("ok", "", compNow)
componentsSynced = compResult.TotalSynced
// Sync pricelists