Improve UI views for assets/components/failures and timeline details

This commit is contained in:
2026-02-15 23:26:12 +03:00
parent 929bf9c524
commit f9ff0e10ff
14 changed files with 1221 additions and 87 deletions

View File

@@ -12,6 +12,11 @@ type InstallationRepository struct {
db *sql.DB
}
type ComponentObservationMeta struct {
ComponentType string
Location string
}
func NewInstallationRepository(db *sql.DB) *InstallationRepository {
return &InstallationRepository{db: db}
}
@@ -92,3 +97,129 @@ func (r *InstallationRepository) ListCurrentComponentIDsByAssets(ctx context.Con
return result, nil
}
func (r *InstallationRepository) ListLatestFirmwareByComponentIDs(ctx context.Context, componentIDs []string) (map[string]string, error) {
result := make(map[string]string, len(componentIDs))
if len(componentIDs) == 0 {
return result, nil
}
placeholders := make([]string, len(componentIDs))
args := make([]any, 0, len(componentIDs))
for i, componentID := range componentIDs {
placeholders[i] = "?"
args = append(args, componentID)
}
query := `SELECT o.part_id, o.firmware_version
FROM observations o
WHERE o.part_id IN (` + strings.Join(placeholders, ",") + `)
AND o.firmware_version IS NOT NULL
AND o.id = (
SELECT o2.id
FROM observations o2
WHERE o2.part_id = o.part_id
AND o2.firmware_version IS NOT NULL
ORDER BY o2.observed_at DESC, o2.created_at DESC, o2.id DESC
LIMIT 1
)`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var componentID string
var firmware string
if err := rows.Scan(&componentID, &firmware); err != nil {
return nil, err
}
result[componentID] = firmware
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (r *InstallationRepository) ListLatestObservationMetaByComponentIDs(ctx context.Context, componentIDs []string) (map[string]ComponentObservationMeta, error) {
result := make(map[string]ComponentObservationMeta, len(componentIDs))
if len(componentIDs) == 0 {
return result, nil
}
placeholders := make([]string, len(componentIDs))
args := make([]any, 0, len(componentIDs))
for i, componentID := range componentIDs {
placeholders[i] = "?"
args = append(args, componentID)
}
query := `SELECT
o.part_id,
JSON_UNQUOTE(JSON_EXTRACT(o.details, '$.component_type')) AS component_type,
COALESCE(
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(o.details, '$.slot')), ''),
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(o.details, '$.attributes.location')), ''),
CASE
WHEN JSON_EXTRACT(o.details, '$.attributes.socket') IS NULL THEN NULL
ELSE CONCAT('Socket ', JSON_UNQUOTE(JSON_EXTRACT(o.details, '$.attributes.socket')))
END,
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(o.details, '$.attributes.bdf')), '')
) AS location
FROM observations o
WHERE o.part_id IN (` + strings.Join(placeholders, ",") + `)
AND o.id = (
SELECT o2.id
FROM observations o2
WHERE o2.part_id = o.part_id
ORDER BY o2.observed_at DESC, o2.created_at DESC, o2.id DESC
LIMIT 1
)`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var componentID string
var componentType sql.NullString
var location sql.NullString
if err := rows.Scan(&componentID, &componentType, &location); err != nil {
return nil, err
}
result[componentID] = ComponentObservationMeta{
ComponentType: strings.TrimSpace(componentType.String),
Location: strings.TrimSpace(location.String),
}
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (r *InstallationRepository) GetCurrentAssetIDByComponent(ctx context.Context, componentID string) (*string, error) {
var assetID string
row := r.db.QueryRowContext(ctx,
`SELECT machine_id
FROM installations
WHERE part_id = ? AND removed_at IS NULL
ORDER BY installed_at DESC, created_at DESC, id DESC
LIMIT 1`,
componentID,
)
if err := row.Scan(&assetID); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return &assetID, nil
}