Add manual failures UI and global list filtering

This commit is contained in:
2026-02-23 17:44:05 +03:00
parent 8aa8b26184
commit eba3b60b48
18 changed files with 2429 additions and 221 deletions

View File

@@ -128,3 +128,32 @@ func (r *FailureRepository) ListAll(ctx context.Context, limit int) ([]domain.Fa
}
return items, nil
}
func (r *FailureRepository) Get(ctx context.Context, id string) (domain.FailureEvent, error) {
row := r.db.QueryRowContext(ctx,
`SELECT id, source, external_id, part_id, machine_id, failure_type, failure_time, details, confidence, created_at
FROM failure_events
WHERE id = ?`,
id,
)
var event domain.FailureEvent
var machineID sql.NullString
var details sql.NullString
var confidence sql.NullFloat64
if err := row.Scan(&event.ID, &event.Source, &event.ExternalID, &event.PartID, &machineID, &event.FailureType, &event.FailureTime, &details, &confidence, &event.CreatedAt); err != nil {
return domain.FailureEvent{}, err
}
if machineID.Valid {
value := machineID.String
event.MachineID = &value
}
if details.Valid {
value := details.String
event.Details = &value
}
if confidence.Valid {
value := confidence.Float64
event.Confidence = &value
}
return event, nil
}

View File

@@ -0,0 +1,119 @@
package registry
import (
"context"
"database/sql"
"strings"
"time"
)
type InstallationHistorySpan struct {
ComponentID string
AssetID string
SlotName *string
InstalledAt time.Time
RemovedAt *time.Time
}
type AssetInstallationRecord struct {
ComponentID string
AssetID string
SlotName *string
InstalledAt time.Time
RemovedAt *time.Time
}
func (r *InstallationRepository) ListInstallationHistoryByComponentIDs(ctx context.Context, componentIDs []string) (map[string][]InstallationHistorySpan, error) {
result := make(map[string][]InstallationHistorySpan, len(componentIDs))
if len(componentIDs) == 0 {
return result, nil
}
placeholders := make([]string, 0, len(componentIDs))
args := make([]any, 0, len(componentIDs))
for _, id := range componentIDs {
id = strings.TrimSpace(id)
if id == "" {
continue
}
placeholders = append(placeholders, "?")
args = append(args, id)
result[id] = []InstallationHistorySpan{}
}
if len(placeholders) == 0 {
return result, nil
}
query := `SELECT part_id, machine_id, slot_name, installed_at, removed_at
FROM installations
WHERE part_id IN (` + strings.Join(placeholders, ",") + `)
ORDER BY part_id, installed_at DESC, created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var row InstallationHistorySpan
var slot sql.NullString
var removedAt sql.NullTime
if err := rows.Scan(&row.ComponentID, &row.AssetID, &slot, &row.InstalledAt, &removedAt); err != nil {
return nil, err
}
row.SlotName = nullStringToPtr(slot)
row.RemovedAt = nullTimeToPtr(removedAt)
result[row.ComponentID] = append(result[row.ComponentID], row)
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (r *InstallationRepository) ListInstallationsByAssetIDsSince(ctx context.Context, assetIDs []string, since time.Time) (map[string][]AssetInstallationRecord, error) {
result := make(map[string][]AssetInstallationRecord, len(assetIDs))
if len(assetIDs) == 0 {
return result, nil
}
placeholders := make([]string, 0, len(assetIDs))
args := make([]any, 0, len(assetIDs)+1)
for _, id := range assetIDs {
id = strings.TrimSpace(id)
if id == "" {
continue
}
placeholders = append(placeholders, "?")
args = append(args, id)
result[id] = []AssetInstallationRecord{}
}
if len(placeholders) == 0 {
return result, nil
}
if since.IsZero() {
since = time.Unix(0, 0).UTC()
}
query := `SELECT part_id, machine_id, slot_name, installed_at, removed_at
FROM installations
WHERE machine_id IN (` + strings.Join(placeholders, ",") + `)
AND installed_at >= ?
ORDER BY machine_id, installed_at DESC, created_at DESC, id DESC`
args = append(args, since.UTC())
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var row AssetInstallationRecord
var slot sql.NullString
var removedAt sql.NullTime
if err := rows.Scan(&row.ComponentID, &row.AssetID, &slot, &row.InstalledAt, &removedAt); err != nil {
return nil, err
}
row.SlotName = nullStringToPtr(slot)
row.RemovedAt = nullTimeToPtr(removedAt)
result[row.AssetID] = append(result[row.AssetID], row)
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}