Files
container-census/internal/plugins/event_bus.go
Self Hosters 8ac9ca8947 Add plugin architecture and NPM integration (WIP)
Plugin system infrastructure:
- Plugin interface with lifecycle management (Init, Start, Stop)
- Plugin manager for registration and route mounting
- Scoped database access for plugin data/settings
- Event bus for plugin communication
- Badge providers and container enrichers

NPM plugin (Nginx Proxy Manager):
- API client with JWT authentication
- Instance management (add/edit/delete/test/sync)
- Proxy host fetching and container matching
- Badge provider for exposed containers
- Tab UI with external JS loading

Container model updates:
- Added NetworkDetails (IP, aliases) for plugin matching
- Added StartedAt timestamp for uptime display
- Added PluginData map for plugin enrichment

Frontend plugin system:
- Plugin manager JS for loading tabs and badges
- Integrations dropdown in navigation
- External script loading with init function callbacks
- Container uptime display on cards

Note: Plugin tab JS execution has issues - Next.js migration planned.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 16:02:53 -05:00

223 lines
5.2 KiB
Go

package plugins
import (
"context"
"sync"
"time"
)
// EventBusImpl implements the EventBus interface
type EventBusImpl struct {
mu sync.RWMutex
subscribers map[string][]eventSubscriber
nextID int
}
type eventSubscriber struct {
id int
handler EventHandler
}
// NewEventBus creates a new event bus
func NewEventBus() *EventBusImpl {
return &EventBusImpl{
subscribers: make(map[string][]eventSubscriber),
}
}
// Subscribe registers a handler for an event type
func (e *EventBusImpl) Subscribe(eventType string, handler EventHandler) func() {
e.mu.Lock()
defer e.mu.Unlock()
id := e.nextID
e.nextID++
e.subscribers[eventType] = append(e.subscribers[eventType], eventSubscriber{
id: id,
handler: handler,
})
// Return unsubscribe function
return func() {
e.mu.Lock()
defer e.mu.Unlock()
subs := e.subscribers[eventType]
for i, sub := range subs {
if sub.id == id {
e.subscribers[eventType] = append(subs[:i], subs[i+1:]...)
break
}
}
}
}
// Publish sends an event to all subscribers
func (e *EventBusImpl) Publish(event Event) {
e.mu.RLock()
// Get subscribers for this event type
subs := make([]eventSubscriber, len(e.subscribers[event.Type]))
copy(subs, e.subscribers[event.Type])
// Also notify wildcard subscribers
wildcardSubs := make([]eventSubscriber, len(e.subscribers["*"]))
copy(wildcardSubs, e.subscribers["*"])
e.mu.RUnlock()
// Set timestamp if not set
if event.Timestamp.IsZero() {
event.Timestamp = time.Now()
}
ctx := context.Background()
// Notify specific subscribers
for _, sub := range subs {
go sub.handler(ctx, event)
}
// Notify wildcard subscribers
for _, sub := range wildcardSubs {
go sub.handler(ctx, event)
}
}
// PublishSync sends an event and waits for all handlers to complete
func (e *EventBusImpl) PublishSync(ctx context.Context, event Event) {
e.mu.RLock()
subs := make([]eventSubscriber, len(e.subscribers[event.Type]))
copy(subs, e.subscribers[event.Type])
wildcardSubs := make([]eventSubscriber, len(e.subscribers["*"]))
copy(wildcardSubs, e.subscribers["*"])
e.mu.RUnlock()
if event.Timestamp.IsZero() {
event.Timestamp = time.Now()
}
var wg sync.WaitGroup
for _, sub := range subs {
wg.Add(1)
go func(s eventSubscriber) {
defer wg.Done()
s.handler(ctx, event)
}(sub)
}
for _, sub := range wildcardSubs {
wg.Add(1)
go func(s eventSubscriber) {
defer wg.Done()
s.handler(ctx, event)
}(sub)
}
wg.Wait()
}
// SubscriberCount returns the number of subscribers for an event type
func (e *EventBusImpl) SubscriberCount(eventType string) int {
e.mu.RLock()
defer e.mu.RUnlock()
return len(e.subscribers[eventType])
}
// Helper functions to create common events
// NewScanCompleteEvent creates a scan complete event
func NewScanCompleteEvent(hostID int64, hostName string, containerCount int) Event {
return Event{
Type: EventScanComplete,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"host_name": hostName,
"container_count": containerCount,
},
}
}
// NewContainerStateChangeEvent creates a container state change event
func NewContainerStateChangeEvent(hostID int64, containerID, containerName, oldState, newState string) Event {
return Event{
Type: EventContainerStateChange,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"container_id": containerID,
"container_name": containerName,
"old_state": oldState,
"new_state": newState,
},
}
}
// NewContainerCreatedEvent creates a container created event
func NewContainerCreatedEvent(hostID int64, containerID, containerName, image string) Event {
return Event{
Type: EventContainerCreated,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"container_id": containerID,
"container_name": containerName,
"image": image,
},
}
}
// NewContainerRemovedEvent creates a container removed event
func NewContainerRemovedEvent(hostID int64, containerID, containerName string) Event {
return Event{
Type: EventContainerRemoved,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"container_id": containerID,
"container_name": containerName,
},
}
}
// NewImageUpdatedEvent creates an image updated event
func NewImageUpdatedEvent(hostID int64, containerID, containerName, oldImageID, newImageID string) Event {
return Event{
Type: EventImageUpdated,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"container_id": containerID,
"container_name": containerName,
"old_image_id": oldImageID,
"new_image_id": newImageID,
},
}
}
// NewHostAddedEvent creates a host added event
func NewHostAddedEvent(hostID int64, hostName, address string) Event {
return Event{
Type: EventHostAdded,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"host_name": hostName,
"address": address,
},
}
}
// NewHostRemovedEvent creates a host removed event
func NewHostRemovedEvent(hostID int64, hostName string) Event {
return Event{
Type: EventHostRemoved,
Timestamp: time.Now(),
Data: map[string]interface{}{
"host_id": hostID,
"host_name": hostName,
},
}
}