Replaces hardcoded JS category filters and config-type buttons with server-pushed settings synced from qt_settings (MariaDB) → local_qt_settings (SQLite). - new table local_qt_settings (AutoMigrate) — synced after component sync - GET /api/configurator-settings returns config_types, tab_config, always_visible_tabs, required_categories with hardcoded fallbacks - new-config modal: type buttons rendered from server data (Сервер/СХД static fallback) - configurator: TAB_CONFIG and category filter driven by server; required-category badge on tabs - SyncQtSettings wired into SyncComponents and SyncAll handlers (non-fatal on old server) - bible-local/server-contract-qt-settings.md — contract for server-side agent - page titles: OFS → QFS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
123 lines
4.1 KiB
Go
123 lines
4.1 KiB
Go
package localdb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ConfigTypeDef describes one device configuration type as synced from qt_settings.
|
|
type ConfigTypeDef struct {
|
|
Code string `json:"code"`
|
|
NameRu string `json:"name_ru"`
|
|
DisplayOrder int `json:"display_order"`
|
|
Categories []string `json:"categories"`
|
|
}
|
|
|
|
// TabSection is a named sub-group of categories within a configurator tab.
|
|
type TabSection struct {
|
|
Title string `json:"title"`
|
|
Categories []string `json:"categories"`
|
|
}
|
|
|
|
// TabDef describes one tab in the configurator as synced from qt_settings.
|
|
type TabDef struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
SingleSelect bool `json:"single_select"`
|
|
Categories []string `json:"categories"`
|
|
Sections []TabSection `json:"sections,omitempty"`
|
|
}
|
|
|
|
// ConfiguratorSettings holds all four server-driven settings consumed by the configurator.
|
|
// Fields are nil/empty when the corresponding qt_settings key is absent or unparseable;
|
|
// callers are expected to apply hardcoded fallbacks in that case.
|
|
type ConfiguratorSettings struct {
|
|
ConfigTypes []ConfigTypeDef `json:"config_types"`
|
|
TabConfig []TabDef `json:"tab_config"`
|
|
AlwaysVisibleTabs []string `json:"always_visible_tabs"`
|
|
RequiredCategories map[string][]string `json:"required_categories"`
|
|
}
|
|
|
|
// SyncQtSettings reads all rows from qt_settings on MariaDB and replaces the
|
|
// local_qt_settings cache in a single SQLite transaction. Returns an error if
|
|
// the qt_settings table doesn't exist on the server (old server without the
|
|
// table) or on any query/write failure.
|
|
func (l *LocalDB) SyncQtSettings(mariaDB *gorm.DB) error {
|
|
var rows []LocalQtSetting
|
|
if err := mariaDB.
|
|
Table("qt_settings").
|
|
Select("name, value").
|
|
Find(&rows).Error; err != nil {
|
|
return fmt.Errorf("reading qt_settings from MariaDB: %w", err)
|
|
}
|
|
|
|
return l.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Exec("DELETE FROM local_qt_settings").Error; err != nil {
|
|
return fmt.Errorf("clearing local_qt_settings: %w", err)
|
|
}
|
|
if len(rows) == 0 {
|
|
return nil
|
|
}
|
|
if err := tx.Create(&rows).Error; err != nil {
|
|
return fmt.Errorf("inserting local_qt_settings: %w", err)
|
|
}
|
|
slog.Info("qt_settings synced", "count", len(rows))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetQtSetting returns the raw JSON value for a named setting.
|
|
// found is false when the key does not exist.
|
|
func (l *LocalDB) GetQtSetting(name string) (value string, found bool, err error) {
|
|
var row LocalQtSetting
|
|
res := l.db.Where("name = ?", name).First(&row)
|
|
if res.Error != nil {
|
|
if res.Error == gorm.ErrRecordNotFound {
|
|
return "", false, nil
|
|
}
|
|
return "", false, res.Error
|
|
}
|
|
return row.Value, true, nil
|
|
}
|
|
|
|
// GetConfiguratorSettings reads all four known settings from local_qt_settings and
|
|
// parses them. Any missing or unparseable key is left as nil/zero in the result;
|
|
// the caller must apply fallbacks.
|
|
func (l *LocalDB) GetConfiguratorSettings() (*ConfiguratorSettings, error) {
|
|
out := &ConfiguratorSettings{}
|
|
|
|
keys := []string{"config_types", "tab_config", "always_visible_tabs", "required_categories"}
|
|
for _, key := range keys {
|
|
raw, found, err := l.GetQtSetting(key)
|
|
if err != nil {
|
|
return out, fmt.Errorf("reading setting %q: %w", key, err)
|
|
}
|
|
if !found || raw == "" {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "config_types":
|
|
if err := json.Unmarshal([]byte(raw), &out.ConfigTypes); err != nil {
|
|
slog.Warn("failed to parse config_types setting", "error", err)
|
|
}
|
|
case "tab_config":
|
|
if err := json.Unmarshal([]byte(raw), &out.TabConfig); err != nil {
|
|
slog.Warn("failed to parse tab_config setting", "error", err)
|
|
}
|
|
case "always_visible_tabs":
|
|
if err := json.Unmarshal([]byte(raw), &out.AlwaysVisibleTabs); err != nil {
|
|
slog.Warn("failed to parse always_visible_tabs setting", "error", err)
|
|
}
|
|
case "required_categories":
|
|
if err := json.Unmarshal([]byte(raw), &out.RequiredCategories); err != nil {
|
|
slog.Warn("failed to parse required_categories setting", "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|