Agent-Status und Inaktivitaets-Erkennung implementiert
- Status wird aus last_heartbeat berechnet (online/stale/offline/unknown)
- GET /agents und GET /agents/{id} liefern status Feld
- agent_events Tabelle fuer Event-Tracking (Migration automatisch)
- Inaktivitaets-Monitor als Goroutine (prueft alle 60s, schreibt Events bei Statuswechsel)
- WebSocket Connect/Disconnect Events werden automatisch geschrieben
- API-Endpoints: GET /agents/events und GET /agents/{id}/events (?limit=N&type=X)
- DB-Methoden: InsertAgentEvent, GetAgentEvents, GetAllAgentEvents, GetAgentStatus
- README aktualisiert (Status-Doku, Events API, Dateistruktur)
This commit is contained in:
@@ -88,6 +88,15 @@ func (d *Database) migrate() error {
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_config_backups_agent ON config_backups(agent_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_config_backups_hash ON config_backups(agent_id, hash);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
||||
event_type TEXT NOT NULL,
|
||||
message TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_events_agent ON agent_events(agent_id, created_at DESC);
|
||||
`
|
||||
|
||||
if _, err := d.db.Exec(schema); err != nil {
|
||||
@@ -631,3 +640,101 @@ func (d *Database) GetMetricsSummary(agentID, metric, bucket string, from, to ti
|
||||
}
|
||||
return results, rows.Err()
|
||||
}
|
||||
|
||||
// --- Agent Events Methoden ---
|
||||
|
||||
// InsertAgentEvent schreibt ein Event in die agent_events Tabelle
|
||||
func (d *Database) InsertAgentEvent(agentID, eventType, message string) error {
|
||||
_, err := d.db.Exec(
|
||||
"INSERT INTO agent_events (agent_id, event_type, message) VALUES ($1, $2, $3)",
|
||||
agentID, eventType, message,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Event schreiben fehlgeschlagen: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAgentEvents holt Events fuer einen bestimmten Agent
|
||||
func (d *Database) GetAgentEvents(agentID string, eventType string, limit int) ([]models.AgentEvent, error) {
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
query := "SELECT id, agent_id, event_type, COALESCE(message, ''), created_at FROM agent_events WHERE agent_id = $1"
|
||||
args := []interface{}{agentID}
|
||||
|
||||
if eventType != "" {
|
||||
query += " AND event_type = $2"
|
||||
args = append(args, eventType)
|
||||
}
|
||||
|
||||
query += fmt.Sprintf(" ORDER BY created_at DESC LIMIT $%d", len(args)+1)
|
||||
args = append(args, limit)
|
||||
|
||||
rows, err := d.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []models.AgentEvent
|
||||
for rows.Next() {
|
||||
var e models.AgentEvent
|
||||
if err := rows.Scan(&e.ID, &e.AgentID, &e.EventType, &e.Message, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
if events == nil {
|
||||
events = []models.AgentEvent{}
|
||||
}
|
||||
return events, rows.Err()
|
||||
}
|
||||
|
||||
// GetAllAgentEvents holt Events fuer alle Agents
|
||||
func (d *Database) GetAllAgentEvents(eventType string, limit int) ([]models.AgentEvent, error) {
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
query := "SELECT id, agent_id, event_type, COALESCE(message, ''), created_at FROM agent_events"
|
||||
args := []interface{}{}
|
||||
|
||||
if eventType != "" {
|
||||
query += " WHERE event_type = $1"
|
||||
args = append(args, eventType)
|
||||
}
|
||||
|
||||
query += fmt.Sprintf(" ORDER BY created_at DESC LIMIT $%d", len(args)+1)
|
||||
args = append(args, limit)
|
||||
|
||||
rows, err := d.db.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []models.AgentEvent
|
||||
for rows.Next() {
|
||||
var e models.AgentEvent
|
||||
if err := rows.Scan(&e.ID, &e.AgentID, &e.EventType, &e.Message, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
if events == nil {
|
||||
events = []models.AgentEvent{}
|
||||
}
|
||||
return events, rows.Err()
|
||||
}
|
||||
|
||||
// GetAgentStatus berechnet den Status eines Agents aus last_heartbeat
|
||||
func (d *Database) GetAgentStatus(agentID string) string {
|
||||
var lastHB sql.NullTime
|
||||
err := d.db.QueryRow("SELECT last_heartbeat FROM agents WHERE id = $1", agentID).Scan(&lastHB)
|
||||
if err != nil || !lastHB.Valid {
|
||||
return "unknown"
|
||||
}
|
||||
return models.CalculateAgentStatus(&lastHB.Time)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user