- Rename project from system-monitor to Tyto (barn owl themed) - Update Go module name and all import paths - Update Docker container names (tyto-backend, tyto-frontend) - Update localStorage keys (tyto-settings, tyto-hosts) - Create barn owl SVG favicon and PWA icons (192, 512) - Update header with owl logo icon - Update manifest.json and app.html with Tyto branding Named after Tyto alba, the barn owl — nature's silent, watchful guardian 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
143 lines
3.1 KiB
Go
143 lines
3.1 KiB
Go
package collectors
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"tyto/internal/models"
|
|
)
|
|
|
|
const dbusTimeout = 2 * time.Second
|
|
|
|
type SystemdCollector struct {
|
|
conn *dbus.Conn
|
|
available bool
|
|
}
|
|
|
|
func NewSystemdCollector() *SystemdCollector {
|
|
c := &SystemdCollector{}
|
|
c.connect()
|
|
return c
|
|
}
|
|
|
|
func (c *SystemdCollector) connect() {
|
|
// Try to connect to the system bus
|
|
conn, err := dbus.SystemBus()
|
|
if err != nil {
|
|
c.available = false
|
|
return
|
|
}
|
|
c.conn = conn
|
|
c.available = true
|
|
}
|
|
|
|
func (c *SystemdCollector) Collect() (models.SystemdStats, error) {
|
|
stats := models.SystemdStats{
|
|
Available: c.available,
|
|
Services: []models.ServiceStatus{},
|
|
}
|
|
|
|
if !c.available || c.conn == nil {
|
|
return stats, nil
|
|
}
|
|
|
|
// Create a context with timeout for the D-Bus call
|
|
ctx, cancel := context.WithTimeout(context.Background(), dbusTimeout)
|
|
defer cancel()
|
|
|
|
// Call ListUnits on systemd manager with timeout
|
|
obj := c.conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
|
|
call := obj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0)
|
|
if call.Err != nil {
|
|
// Connection might have been lost or timed out, mark as unavailable
|
|
c.available = false
|
|
return stats, nil // Return empty stats instead of error
|
|
}
|
|
|
|
// ListUnits returns array of structs:
|
|
// (name, description, load_state, active_state, sub_state, following, unit_path, job_id, job_type, job_path)
|
|
var units [][]interface{}
|
|
if err := call.Store(&units); err != nil {
|
|
return stats, nil // Return empty stats instead of error
|
|
}
|
|
|
|
for _, unit := range units {
|
|
if len(unit) < 5 {
|
|
continue
|
|
}
|
|
|
|
name, ok := unit[0].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Only process .service units
|
|
if !strings.HasSuffix(name, ".service") {
|
|
continue
|
|
}
|
|
|
|
// Remove .service suffix for cleaner display
|
|
name = strings.TrimSuffix(name, ".service")
|
|
|
|
// Skip template services
|
|
if strings.Contains(name, "@") && !strings.Contains(name, "@.") {
|
|
continue
|
|
}
|
|
|
|
load, _ := unit[2].(string)
|
|
active, _ := unit[3].(string)
|
|
sub, _ := unit[4].(string)
|
|
|
|
service := models.ServiceStatus{
|
|
Name: name,
|
|
Load: load,
|
|
Active: active,
|
|
Sub: sub,
|
|
}
|
|
|
|
// Count by status
|
|
switch active {
|
|
case "active":
|
|
stats.Active++
|
|
case "inactive":
|
|
stats.Inactive++
|
|
case "failed":
|
|
stats.Failed++
|
|
}
|
|
|
|
stats.Services = append(stats.Services, service)
|
|
}
|
|
|
|
stats.Total = len(stats.Services)
|
|
|
|
// Sort failed services first, then active running, then other active
|
|
// Limit to 50 most relevant services
|
|
sortedServices := make([]models.ServiceStatus, 0, 50)
|
|
|
|
// Add failed first
|
|
for _, s := range stats.Services {
|
|
if s.Active == "failed" && len(sortedServices) < 50 {
|
|
sortedServices = append(sortedServices, s)
|
|
}
|
|
}
|
|
|
|
// Add active running
|
|
for _, s := range stats.Services {
|
|
if s.Active == "active" && s.Sub == "running" && len(sortedServices) < 50 {
|
|
sortedServices = append(sortedServices, s)
|
|
}
|
|
}
|
|
|
|
// Add other active
|
|
for _, s := range stats.Services {
|
|
if s.Active == "active" && s.Sub != "running" && len(sortedServices) < 50 {
|
|
sortedServices = append(sortedServices, s)
|
|
}
|
|
}
|
|
|
|
stats.Services = sortedServices
|
|
return stats, nil
|
|
}
|