feat(controller): add per-node notify switch controller
This commit is contained in:
+31
-6
@@ -5,8 +5,27 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// botNodeConfigMu guards botNodeConfig, the in-memory mirror of
|
||||||
|
// bot_node_config.json.
|
||||||
|
var (
|
||||||
|
botNodeConfigMu sync.RWMutex
|
||||||
|
botNodeConfig BotNodeMembers
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeStatusNotifyEnabled reports whether the node identified by uuid should
|
||||||
|
// broadcast status-change notifications, per bot_node_config.json. The flag is
|
||||||
|
// allow-list only: a node notifies when its entry has enableStatusNotify set to
|
||||||
|
// true, and stays silent when the entry is absent or not enabled.
|
||||||
|
func NodeStatusNotifyEnabled(uuid string) bool {
|
||||||
|
botNodeConfigMu.RLock()
|
||||||
|
opts, ok := botNodeConfig[uuid]
|
||||||
|
botNodeConfigMu.RUnlock()
|
||||||
|
return ok && opts.EnableStatusNotify
|
||||||
|
}
|
||||||
|
|
||||||
// LoadGlobalConfig reads and parses the configuration file, applies defaults,
|
// LoadGlobalConfig reads and parses the configuration file, applies defaults,
|
||||||
// and stores it as a global singleton.
|
// and stores it as a global singleton.
|
||||||
func LoadGlobalConfig(configPath string) (*Config, error) {
|
func LoadGlobalConfig(configPath string) (*Config, error) {
|
||||||
@@ -116,12 +135,14 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) {
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveBotNodeConfig persists the given node UUIDs to the bot node config file.
|
// SaveBotNodeConfig persists the given node UUIDs to the bot node config file
|
||||||
// Entries already present in the file are always preserved: a UUID Komari no
|
// and refreshes the in-memory mirror read by NodeStatusNotifyEnabled. Entries
|
||||||
// longer reports on a given fetch is kept rather than deleted, so per-node
|
// already present in the file are always preserved: a UUID Komari no longer
|
||||||
// settings for stale nodes survive a node-list refresh that does not include
|
// reports on a given fetch is kept rather than deleted, so per-node settings
|
||||||
// them. UUIDs seen for the first time are added with an empty BotNodeOptions
|
// for stale nodes survive a node-list refresh that does not include them. UUIDs
|
||||||
// entry. The resulting JSON has its object keys emitted in sorted order by
|
// seen for the first time are added disabled (enableStatusNotify defaults to
|
||||||
|
// false); enable a node's status notifications by setting the flag to true in
|
||||||
|
// the file. The resulting JSON has its object keys emitted in sorted order by
|
||||||
// encoding/json, keeping the file deterministic across writes. The path is
|
// encoding/json, keeping the file deterministic across writes. The path is
|
||||||
// supplied by the caller (typically global.ConfigPath.BotNodeConfig).
|
// supplied by the caller (typically global.ConfigPath.BotNodeConfig).
|
||||||
func SaveBotNodeConfig(configPath string, uuids []string) error {
|
func SaveBotNodeConfig(configPath string, uuids []string) error {
|
||||||
@@ -151,6 +172,10 @@ func SaveBotNodeConfig(configPath string, uuids []string) error {
|
|||||||
if err := os.WriteFile(configPath, data, 0o644); err != nil {
|
if err := os.WriteFile(configPath, data, 0o644); err != nil {
|
||||||
return fmt.Errorf("failed to write bot node config %s: %w", configPath, err)
|
return fmt.Errorf("failed to write bot node config %s: %w", configPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botNodeConfigMu.Lock()
|
||||||
|
botNodeConfig = members
|
||||||
|
botNodeConfigMu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -172,14 +172,17 @@ type BotUserConfig struct {
|
|||||||
var C_botUserConfig *BotUserConfig
|
var C_botUserConfig *BotUserConfig
|
||||||
|
|
||||||
// BotNodeOptions holds per-node options stored in bot_node_config.json. The
|
// BotNodeOptions holds per-node options stored in bot_node_config.json. The
|
||||||
// struct is currently empty; per-node settings can be added here later without
|
// file is auto-populated by the node tracker for every node Komari reports;
|
||||||
// invalidating existing files.
|
// per-node options are edited by hand in the JSON file.
|
||||||
type BotNodeOptions struct {
|
type BotNodeOptions struct {
|
||||||
|
// EnableStatusNotify is an allow-list switch: a node broadcasts status-change
|
||||||
|
// notifications only when this is true. Nodes default to false (silent)
|
||||||
|
// unless explicitly enabled.
|
||||||
|
EnableStatusNotify bool `json:"enableStatusNotify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BotNodeMembers maps a node UUID (as reported by Komari) to its per-node
|
// BotNodeMembers maps a node UUID (as reported by Komari) to its per-node
|
||||||
// options. A plain struct value marshals to an empty JSON object, so
|
// options, e.g. bot_node_config.json:
|
||||||
// bot_node_config.json reads like:
|
|
||||||
//
|
//
|
||||||
// {"<uuid1>": {}, "<uuid2>": {}}
|
// {"<uuid1>": {"enableStatusNotify": true}, "<uuid2>": {}}
|
||||||
type BotNodeMembers map[string]BotNodeOptions
|
type BotNodeMembers map[string]BotNodeOptions
|
||||||
@@ -165,26 +165,26 @@ func (m *Manager) ShowBotServerList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyStatusChange sends a status change notification to all enabled controllers.
|
// NotifyStatusChange sends a status change notification to all enabled
|
||||||
|
// controllers. It honors the per-node allow-list in bot_node_config.json: a
|
||||||
|
// node whose enableStatusNotify is not true is skipped entirely, so no
|
||||||
|
// controller (chat bots or notification pipes) broadcasts its change.
|
||||||
func (m *Manager) NotifyStatusChange(change node.StatusChange) {
|
func (m *Manager) NotifyStatusChange(change node.StatusChange) {
|
||||||
|
if !config.NodeStatusNotifyEnabled(change.UUID) {
|
||||||
|
postLog.Debug(fmt.Sprintf("Status change for node %s skipped: enableStatusNotify is not enabled", change.UUID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
cfg := config.C_globalConfig
|
|
||||||
templateStr := cfg.ControllerMessage.ServerStatusChanged
|
|
||||||
params := template.BuildParamsFromStatusChange(change)
|
|
||||||
|
|
||||||
for _, ctrl := range m.controllers {
|
for _, ctrl := range m.controllers {
|
||||||
if !ctrl.IsEnabled() {
|
if !ctrl.IsEnabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the names of controllers that support commands for the message.
|
|
||||||
if err := ctrl.SendStatusChange(change); err != nil {
|
if err := ctrl.SendStatusChange(change); err != nil {
|
||||||
postLog.Warning(fmt.Sprintf("Controller %s failed to send status change: %v", ctrl.Name(), err))
|
postLog.Warning(fmt.Sprintf("Controller %s failed to send status change: %v", ctrl.Name(), err))
|
||||||
}
|
}
|
||||||
_ = templateStr
|
|
||||||
_ = params
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user