1317ff6369
Operations are now clickable from the host detail page, linking to
/ops/{id} which shows the operation info, host link, duration, and
activity log filtered to that operation. Active operations can be
cancelled, which transitions the host to failed and releases the lock.
SSE activity events now include operation_id for real-time filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"provisioning/internal/model"
|
|
)
|
|
|
|
type Activity struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
func (s *Activity) Log(ctx context.Context, hostID, opID int64, level model.LogLevel, source, message string) (int64, error) {
|
|
res, err := s.DB.ExecContext(ctx, `
|
|
INSERT INTO activity_log(host_id, operation_id, level, message, source)
|
|
VALUES(?,?,?,?,?)
|
|
`, hostID, nullInt64(opID), level, message, source)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("insert activity: %w", err)
|
|
}
|
|
return res.LastInsertId()
|
|
}
|
|
|
|
func (s *Activity) ListByOperation(ctx context.Context, operationID int64, limit int) ([]model.ActivityEntry, error) {
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
rows, err := s.DB.QueryContext(ctx, `
|
|
SELECT id, host_id, COALESCE(operation_id, 0), level, message, source, created_at
|
|
FROM activity_log WHERE operation_id = ? ORDER BY created_at DESC LIMIT ?
|
|
`, operationID, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list activity by operation: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
var out []model.ActivityEntry
|
|
for rows.Next() {
|
|
var e model.ActivityEntry
|
|
var createdAt string
|
|
if err := rows.Scan(&e.ID, &e.HostID, &e.OperationID, &e.Level, &e.Message, &e.Source, &createdAt); err != nil {
|
|
return nil, fmt.Errorf("scan activity: %w", err)
|
|
}
|
|
e.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
|
out = append(out, e)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Activity) ListByHost(ctx context.Context, hostID int64, limit int) ([]model.ActivityEntry, error) {
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
rows, err := s.DB.QueryContext(ctx, `
|
|
SELECT id, host_id, COALESCE(operation_id, 0), level, message, source, created_at
|
|
FROM activity_log WHERE host_id = ? ORDER BY created_at DESC LIMIT ?
|
|
`, hostID, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list activity: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
var out []model.ActivityEntry
|
|
for rows.Next() {
|
|
var e model.ActivityEntry
|
|
var createdAt string
|
|
if err := rows.Scan(&e.ID, &e.HostID, &e.OperationID, &e.Level, &e.Message, &e.Source, &createdAt); err != nil {
|
|
return nil, fmt.Errorf("scan activity: %w", err)
|
|
}
|
|
e.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
|
out = append(out, e)
|
|
}
|
|
return out, rows.Err()
|
|
}
|