Files
kportal/internal/events/bus.go
T
lukaszraczylo 96ae1d45e0 style: Extract UI constants and refactor main view rendering (#30)
- [x] Add golangci-lint configuration with gocritic ifElseChain disabled
- [x] Rename error variables to avoid shadowing (createErr, watcherErr, watchErr, etc.)
- [x] Replace `interface{}` with `any` type alias throughout codebase
- [x] Add package-level documentation comments to all internal packages
- [x] Reorder struct fields alphabetically for consistency
- [x] Extract UI constants (terminal dimensions, column widths, colors) to constants.go
- [x] Refactor BubbleTeaUI main view rendering into smaller helper functions
- [x] Simplify nested conditionals and improve code clarity
- [x] Add `isForwardDisabled()` helper method to BubbleTeaUI
- [x] Update file permissions from 0644 to 0600 in config tests
- [x] Add `#nosec` comments and error suppression where appropriate
- [x] Improve test table struct field ordering for readability
- [x] Fix resource parsing in AddForward using strings.SplitN
- [x] Add comprehensive tests for new UI helper functions and constants
2026-01-13 09:37:45 +00:00

189 lines
4.5 KiB
Go

// Package events provides a publish-subscribe event bus for decoupled
// communication between kportal components. Events are typed and carry
// contextual data about forward lifecycle, health status, and configuration
// changes.
//
// Event types include:
// - Forward lifecycle: starting, connected, disconnected, reconnecting, stopped, error
// - Health: status_changed, stale
// - Watchdog: worker_hung
// - Config: reloaded
//
// Basic usage:
//
// bus := events.NewBus()
// bus.Subscribe(events.EventForwardConnected, func(e events.Event) {
// fmt.Printf("Forward %s connected\n", e.ForwardID)
// })
// bus.Publish(events.Event{Type: events.EventForwardConnected, ForwardID: "..."})
package events
import (
"sync"
)
// EventType represents the type of event
type EventType string
const (
// Forward lifecycle events
EventForwardStarting EventType = "forward.starting"
EventForwardConnected EventType = "forward.connected"
EventForwardDisconnected EventType = "forward.disconnected"
EventForwardReconnecting EventType = "forward.reconnecting"
EventForwardStopped EventType = "forward.stopped"
EventForwardError EventType = "forward.error"
// Health events
EventHealthStatusChanged EventType = "health.status_changed"
EventHealthStale EventType = "health.stale"
// Watchdog events
EventWorkerHung EventType = "watchdog.worker_hung"
// Config events
EventConfigReloaded EventType = "config.reloaded"
)
// Event represents a system event
type Event struct {
Data map[string]interface{}
Type EventType
ForwardID string
}
// Handler is a function that handles events
type Handler func(event Event)
// Bus is a simple event bus for decoupled communication between components
type Bus struct {
handlers map[EventType][]Handler
mu sync.RWMutex
closed bool
}
// NewBus creates a new event bus
func NewBus() *Bus {
return &Bus{
handlers: make(map[EventType][]Handler),
}
}
// Subscribe registers a handler for a specific event type
func (b *Bus) Subscribe(eventType EventType, handler Handler) {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
b.handlers[eventType] = append(b.handlers[eventType], handler)
}
// SubscribeAll registers a handler for all events
func (b *Bus) SubscribeAll(handler Handler) {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
// Subscribe to all known event types
eventTypes := []EventType{
EventForwardStarting,
EventForwardConnected,
EventForwardDisconnected,
EventForwardReconnecting,
EventForwardStopped,
EventForwardError,
EventHealthStatusChanged,
EventHealthStale,
EventWorkerHung,
EventConfigReloaded,
}
for _, et := range eventTypes {
b.handlers[et] = append(b.handlers[et], handler)
}
}
// Publish sends an event to all registered handlers
// Handlers are called synchronously in the order they were registered
func (b *Bus) Publish(event Event) {
b.mu.RLock()
if b.closed {
b.mu.RUnlock()
return
}
handlers := make([]Handler, len(b.handlers[event.Type]))
copy(handlers, b.handlers[event.Type])
b.mu.RUnlock()
for _, handler := range handlers {
handler(event)
}
}
// PublishAsync sends an event to all registered handlers asynchronously
func (b *Bus) PublishAsync(event Event) {
b.mu.RLock()
if b.closed {
b.mu.RUnlock()
return
}
handlers := make([]Handler, len(b.handlers[event.Type]))
copy(handlers, b.handlers[event.Type])
b.mu.RUnlock()
for _, handler := range handlers {
go handler(event)
}
}
// Close stops the event bus and prevents new subscriptions/publications
func (b *Bus) Close() {
b.mu.Lock()
defer b.mu.Unlock()
b.closed = true
b.handlers = make(map[EventType][]Handler)
}
// Helper functions for creating common events
// NewHealthEvent creates a health status change event
func NewHealthEvent(forwardID string, status string, errorMsg string) Event {
return Event{
Type: EventHealthStatusChanged,
ForwardID: forwardID,
Data: map[string]interface{}{
"status": status,
"error_msg": errorMsg,
},
}
}
// NewStaleEvent creates a stale connection event
func NewStaleEvent(forwardID string, reason string) Event {
return Event{
Type: EventHealthStale,
ForwardID: forwardID,
Data: map[string]interface{}{
"reason": reason,
},
}
}
// NewWorkerHungEvent creates a hung worker event
func NewWorkerHungEvent(forwardID string, timeSinceHeartbeat string) Event {
return Event{
Type: EventWorkerHung,
ForwardID: forwardID,
Data: map[string]interface{}{
"time_since_heartbeat": timeSinceHeartbeat,
},
}
}