feat: complete 2.0.7.12 platform overhaul
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
func (s *Store) ListSystemLogsPage(filters SystemLogFilters) (SystemLogPage, error) {
|
||||
page := filters.Page
|
||||
@@ -19,209 +14,71 @@ func (s *Store) ListSystemLogsPage(filters SystemLogFilters) (SystemLogPage, err
|
||||
if perPage > 100 {
|
||||
perPage = 100
|
||||
}
|
||||
items, err := s.collectSystemLogs(filters)
|
||||
base := systemLogUnionSQL()
|
||||
where, args := systemLogWhere(filters)
|
||||
var total int
|
||||
if err := s.queryRow(`SELECT COUNT(*) FROM (`+base+`) system_logs`+where, args...).Scan(&total); err != nil {
|
||||
return SystemLogPage{}, err
|
||||
}
|
||||
queryArgs := append(append([]any{}, args...), perPage, (page-1)*perPage)
|
||||
rows, err := s.query(`SELECT id, category, type, target, status, message, detail, created_at
|
||||
FROM (`+base+`) system_logs`+where+` ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`, queryArgs...)
|
||||
if err != nil {
|
||||
return SystemLogPage{}, err
|
||||
}
|
||||
sort.SliceStable(items, func(i, j int) bool {
|
||||
if items[i].CreatedAt == items[j].CreatedAt {
|
||||
return items[i].ID > items[j].ID
|
||||
}
|
||||
return items[i].CreatedAt > items[j].CreatedAt
|
||||
})
|
||||
total := len(items)
|
||||
start := (page - 1) * perPage
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
end := start + perPage
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
return SystemLogPage{Items: items[start:end], Total: total, Page: page, PerPage: perPage}, nil
|
||||
}
|
||||
|
||||
func (s *Store) collectSystemLogs(filters SystemLogFilters) ([]SystemLogItem, error) {
|
||||
items := []SystemLogItem{}
|
||||
appendItems := func(category string, next []SystemLogItem, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range next {
|
||||
if matchesSystemLog(filters, item, category) {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
logs, err := s.operationLogs()
|
||||
if err := appendItems("operation", logs, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs, err = s.healthLogs()
|
||||
if err := appendItems("health", logs, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs, err = s.clientCallLogs()
|
||||
if err := appendItems("client", logs, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs, err = s.databaseSyncLogs()
|
||||
if err := appendItems("database_sync", logs, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs, err = s.legacySyncLogs()
|
||||
if err := appendItems("legacy_sync", logs, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *Store) operationLogs() ([]SystemLogItem, error) {
|
||||
rows, err := s.query(`SELECT id, actor, type, target, message, ip, user_agent, created_at FROM audit_logs ORDER BY id DESC LIMIT 500`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SystemLogItem{}
|
||||
for rows.Next() {
|
||||
var item AuditLog
|
||||
if err := rows.Scan(&item.ID, &item.Actor, &item.Type, &item.Target, &item.Message, &item.IP, &item.UserAgent, &item.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
var item SystemLogItem
|
||||
if err := rows.Scan(&item.ID, &item.Category, &item.Type, &item.Target, &item.Status, &item.Message, &item.Detail, &item.CreatedAt); err != nil {
|
||||
return SystemLogPage{}, err
|
||||
}
|
||||
items = append(items, SystemLogItem{
|
||||
ID: item.ID,
|
||||
Category: "operation",
|
||||
Type: item.Type,
|
||||
Target: item.Target,
|
||||
Status: firstNonEmpty(item.Actor, "system"),
|
||||
Message: item.Message,
|
||||
Detail: strings.TrimSpace(item.IP + " " + item.UserAgent),
|
||||
CreatedAt: item.CreatedAt,
|
||||
})
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, rows.Err()
|
||||
if err := rows.Err(); err != nil {
|
||||
return SystemLogPage{}, err
|
||||
}
|
||||
return SystemLogPage{Items: items, Total: total, Page: page, PerPage: perPage}, nil
|
||||
}
|
||||
|
||||
func (s *Store) healthLogs() ([]SystemLogItem, error) {
|
||||
rows, err := s.query(`SELECT h.id, h.source_db_id, COALESCE(e.source_id, ''), COALESCE(e.name, ''), h.status, h.latency_ms, h.error, h.checked_at
|
||||
func systemLogUnionSQL() string {
|
||||
return `SELECT id, 'operation' AS category, type, target,
|
||||
COALESCE(NULLIF(actor, ''), 'system') AS status, message, user_agent AS detail, created_at
|
||||
FROM audit_logs
|
||||
UNION ALL
|
||||
SELECT h.id, 'health' AS category, 'endpoint.health' AS type,
|
||||
COALESCE(NULLIF(e.source_id, ''), 'unknown') AS target, h.status,
|
||||
COALESCE(NULLIF(e.name, ''), 'unknown endpoint') AS message, h.error AS detail, h.checked_at AS created_at
|
||||
FROM endpoint_health_checks h LEFT JOIN source_endpoints e ON e.id = h.source_db_id
|
||||
ORDER BY h.id DESC LIMIT 500`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SystemLogItem{}
|
||||
for rows.Next() {
|
||||
var id, sourceDBID int64
|
||||
var sourceID, name, status, message, checkedAt string
|
||||
var latency int
|
||||
if err := rows.Scan(&id, &sourceDBID, &sourceID, &name, &status, &latency, &message, &checkedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := firstNonEmpty(sourceID, fmt.Sprintf("source#%d", sourceDBID))
|
||||
items = append(items, SystemLogItem{
|
||||
ID: id,
|
||||
Category: "health",
|
||||
Type: "endpoint.health",
|
||||
Target: target,
|
||||
Status: status,
|
||||
Message: firstNonEmpty(name, target),
|
||||
Detail: fmt.Sprintf("%dms %s", latency, message),
|
||||
CreatedAt: checkedAt,
|
||||
})
|
||||
}
|
||||
return items, rows.Err()
|
||||
UNION ALL
|
||||
SELECT id, 'client' AS category, 'endpoint.call' AS type, source_id AS target,
|
||||
status, error AS message, client AS detail, created_at
|
||||
FROM endpoint_call_logs
|
||||
UNION ALL
|
||||
SELECT id, 'database_sync' AS category, direction AS type, direction AS target,
|
||||
status, message, tables_json AS detail, COALESCE(NULLIF(finished_at, ''), started_at) AS created_at
|
||||
FROM database_sync_jobs
|
||||
UNION ALL
|
||||
SELECT id, 'legacy_sync' AS category, 'legacy.sync' AS type, 'legacy' AS target,
|
||||
status, summary AS message, stats_json AS detail, COALESCE(NULLIF(finished_at, ''), started_at) AS created_at
|
||||
FROM legacy_sync_jobs`
|
||||
}
|
||||
|
||||
func (s *Store) clientCallLogs() ([]SystemLogItem, error) {
|
||||
rows, err := s.query(`SELECT id, source_id, status, latency_ms, error, client, created_at FROM endpoint_call_logs ORDER BY id DESC LIMIT 500`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func systemLogWhere(filters SystemLogFilters) (string, []any) {
|
||||
clauses := []string{}
|
||||
args := []any{}
|
||||
if category := strings.TrimSpace(filters.Category); category != "" {
|
||||
clauses = append(clauses, "category = ?")
|
||||
args = append(args, sanitize(category))
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SystemLogItem{}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var sourceID, status, message, client, createdAt string
|
||||
var latency int
|
||||
if err := rows.Scan(&id, &sourceID, &status, &latency, &message, &client, &createdAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, SystemLogItem{
|
||||
ID: id,
|
||||
Category: "client",
|
||||
Type: "endpoint.call",
|
||||
Target: sourceID,
|
||||
Status: status,
|
||||
Message: message,
|
||||
Detail: fmt.Sprintf("%dms %s", latency, client),
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
if query := strings.TrimSpace(filters.Query); query != "" {
|
||||
like := "%" + strings.ToLower(sanitize(query)) + "%"
|
||||
clauses = append(clauses, `(LOWER(category) LIKE ? OR LOWER(type) LIKE ? OR LOWER(target) LIKE ? OR
|
||||
LOWER(status) LIKE ? OR LOWER(message) LIKE ? OR LOWER(detail) LIKE ? OR LOWER(created_at) LIKE ?)`)
|
||||
args = append(args, like, like, like, like, like, like, like)
|
||||
}
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) databaseSyncLogs() ([]SystemLogItem, error) {
|
||||
rows, err := s.query(`SELECT id, direction, status, message, tables_json, started_at, finished_at FROM database_sync_jobs ORDER BY id DESC LIMIT 500`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SystemLogItem{}
|
||||
for rows.Next() {
|
||||
job, err := scanDatabaseSyncJob(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
detail, _ := json.Marshal(map[string]any{"tables": job.Tables, "warnings": job.Warnings, "errors": job.Errors})
|
||||
items = append(items, SystemLogItem{
|
||||
ID: job.ID,
|
||||
Category: "database_sync",
|
||||
Type: job.Direction,
|
||||
Target: directionLabel(job.Direction),
|
||||
Status: job.Status,
|
||||
Message: strings.Join(job.Output, "\n"),
|
||||
Detail: string(detail),
|
||||
CreatedAt: firstNonEmpty(job.FinishedAt, job.StartedAt),
|
||||
})
|
||||
}
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) legacySyncLogs() ([]SystemLogItem, error) {
|
||||
rows, err := s.query(`SELECT id, status, summary, stats_json, started_at, finished_at FROM legacy_sync_jobs ORDER BY id DESC LIMIT 500`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SystemLogItem{}
|
||||
for rows.Next() {
|
||||
var item LegacySyncJob
|
||||
if err := rows.Scan(&item.ID, &item.Status, &item.Summary, &item.StatsJSON, &item.StartedAt, &item.FinishedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, SystemLogItem{
|
||||
ID: item.ID,
|
||||
Category: "legacy_sync",
|
||||
Type: "legacy.sync",
|
||||
Target: "legacy",
|
||||
Status: item.Status,
|
||||
Message: item.Summary,
|
||||
Detail: item.StatsJSON,
|
||||
CreatedAt: firstNonEmpty(item.FinishedAt, item.StartedAt),
|
||||
})
|
||||
}
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
func matchesSystemLog(filters SystemLogFilters, item SystemLogItem, category string) bool {
|
||||
if value := strings.TrimSpace(filters.Category); value != "" && value != category && value != item.Category {
|
||||
return false
|
||||
}
|
||||
if value := strings.ToLower(strings.TrimSpace(filters.Query)); value != "" {
|
||||
haystack := strings.ToLower(strings.Join([]string{item.Category, item.Type, item.Target, item.Status, item.Message, item.Detail, item.CreatedAt}, " "))
|
||||
return strings.Contains(haystack, value)
|
||||
}
|
||||
return true
|
||||
if len(clauses) == 0 {
|
||||
return "", args
|
||||
}
|
||||
return " WHERE " + strings.Join(clauses, " AND "), args
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user