diff options
| author | sinner <[email protected]> | 2026-08-05 01:21:17 +0000 |
|---|---|---|
| committer | sinner <[email protected]> | 2026-08-05 01:21:17 +0000 |
| commit | 4c6c8e86565ac49d1bd1c64e17c1404f4517d200 (patch) | |
| tree | 9f079e106a3f01f5d227c407e3d984a38bb2a8e9 /internal/formatter/usage.go | |
| parent | 2875e578f5e22ae0971d057819f1ab4362b4d115 (diff) | |
| download | dborg-4c6c8e86565ac49d1bd1c64e17c1404f4517d200.tar.gz dborg-4c6c8e86565ac49d1bd1c64e17c1404f4517d200.zip | |
Diffstat (limited to 'internal/formatter/usage.go')
| -rw-r--r-- | internal/formatter/usage.go | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/internal/formatter/usage.go b/internal/formatter/usage.go new file mode 100644 index 0000000..6732a3b --- /dev/null +++ b/internal/formatter/usage.go @@ -0,0 +1,202 @@ +package formatter + +import ( + "fmt" + "strings" + "time" + + "git.db.org.ai/dborg/internal/models" +) + +// FormatCreditUsageLogs renders raw credit_usage_log rows. +func FormatCreditUsageLogs(resp *models.CreditUsageLogsResponse, asJSON bool) (string, error) { + if asJSON { + if err := PrintColorizedJSON(resp); err != nil { + return "", err + } + return "", nil + } + + if len(resp.Logs) == 0 { + return fmt.Sprintf("%s\n", Gray("No usage logs found")), nil + } + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Credit Usage Logs")))) + sb.WriteString(fmt.Sprintf("%s\n\n", Gray(strings.Repeat("─", 90)))) + + table := NewTable([]string{"ID", "Account", "Endpoint", "Path", "Cost", "Charged", "Status", "Time"}) + + for _, row := range resp.Logs { + statusStr := Green(fmt.Sprintf("%d", row.StatusCode)) + if row.StatusCode >= 400 { + statusStr = Red(fmt.Sprintf("%d", row.StatusCode)) + } else if row.StatusCode >= 300 { + statusStr = Yellow(fmt.Sprintf("%d", row.StatusCode)) + } + + chargedStr := Gray("0") + if row.CreditsCharged > 0 { + chargedStr = Yellow(fmt.Sprintf("%d", row.CreditsCharged)) + } + + timeStr := row.CreatedAt + if t, err := time.Parse(time.RFC3339, row.CreatedAt); err == nil { + timeStr = t.Format("01-02 15:04:05") + } else if t, err := time.Parse("2006-01-02T15:04:05Z", row.CreatedAt); err == nil { + timeStr = t.Format("01-02 15:04:05") + } + + table.AddRow( + Gray(fmt.Sprintf("%d", row.ID)), + Bold(row.AccountName), + Cyan(row.Endpoint), + Dim(truncate(row.Path, 30)), + Gray(fmt.Sprintf("%d", row.EndpointCost)), + chargedStr, + statusStr, + Gray(timeStr), + ) + } + + sb.WriteString(table.Render()) + sb.WriteString(fmt.Sprintf("\n%s %d rows\n", Blue("Total:"), resp.Count)) + return sb.String(), nil +} + +// FormatCreditUsageByEndpoint renders per-endpoint aggregate stats. +func FormatCreditUsageByEndpoint(resp *models.CreditUsageByEndpointResponse, asJSON bool) (string, error) { + if asJSON { + if err := PrintColorizedJSON(resp); err != nil { + return "", err + } + return "", nil + } + + if len(resp.Endpoints) == 0 { + return fmt.Sprintf("%s\n", Gray("No endpoint usage data found")), nil + } + + fromStr, toStr := formatTimeRange(resp.From, resp.To) + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Usage by Endpoint")))) + sb.WriteString(fmt.Sprintf("%s %s → %s\n\n", Gray(strings.Repeat("─", 60)), Gray(fromStr), Gray(toStr))) + + table := NewTable([]string{"Endpoint", "Total", "Success", "Cost", "Charged"}) + + var totalCalls, totalCharged int64 + for _, row := range resp.Endpoints { + successRate := "" + if row.TotalCalls > 0 { + pct := float64(row.SuccessCalls) / float64(row.TotalCalls) * 100 + color := Green + if pct < 50 { + color = Red + } else if pct < 80 { + color = Yellow + } + successRate = color(fmt.Sprintf("%d (%.0f%%)", row.SuccessCalls, pct)) + } + + chargedStr := Gray("0") + if row.TotalCharged > 0 { + chargedStr = Yellow(fmt.Sprintf("%d", row.TotalCharged)) + } + + table.AddRow( + Cyan(row.Endpoint), + Bold(fmt.Sprintf("%d", row.TotalCalls)), + successRate, + Gray(fmt.Sprintf("%d ea", row.EndpointCost)), + chargedStr, + ) + totalCalls += row.TotalCalls + totalCharged += row.TotalCharged + } + + sb.WriteString(table.Render()) + sb.WriteString(fmt.Sprintf("\n%s %d calls %s %d credits charged\n", + Blue("Total calls:"), totalCalls, + Blue("Total charged:"), totalCharged, + )) + return sb.String(), nil +} + +// FormatCreditUsageByAccount renders per-account per-endpoint aggregate stats. +func FormatCreditUsageByAccount(resp *models.CreditUsageByAccountResponse, asJSON bool) (string, error) { + if asJSON { + if err := PrintColorizedJSON(resp); err != nil { + return "", err + } + return "", nil + } + + if len(resp.Accounts) == 0 { + return fmt.Sprintf("%s\n", Gray("No account usage data found")), nil + } + + fromStr, toStr := formatTimeRange(resp.From, resp.To) + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Usage by Account")))) + sb.WriteString(fmt.Sprintf("%s %s → %s\n\n", Gray(strings.Repeat("─", 60)), Gray(fromStr), Gray(toStr))) + + table := NewTable([]string{"Account", "Endpoint", "Calls", "Charged", "Last Call"}) + + // Group consecutive rows by account for visual separation + var prevAccount string + var totalCalls, totalCharged int64 + for _, row := range resp.Accounts { + nameStr := Bold(row.AccountName) + if row.AccountName == prevAccount { + nameStr = Dim(" ↳") + } + prevAccount = row.AccountName + + chargedStr := Gray("0") + if row.TotalCharged > 0 { + chargedStr = Yellow(fmt.Sprintf("%d", row.TotalCharged)) + } + + lastCall := row.LastCall + if t, err := time.Parse("2006-01-02 15:04:05", row.LastCall); err == nil { + lastCall = t.Format("2006-01-02 15:04") + } + + table.AddRow( + nameStr, + Cyan(row.Endpoint), + fmt.Sprintf("%d", row.TotalCalls), + chargedStr, + Gray(lastCall), + ) + totalCalls += row.TotalCalls + totalCharged += row.TotalCharged + } + + sb.WriteString(table.Render()) + sb.WriteString(fmt.Sprintf("\n%s %d %s %d credits charged\n", + Blue("Total calls:"), totalCalls, + Blue("Total charged:"), totalCharged, + )) + return sb.String(), nil +} + +func formatTimeRange(from, to string) (string, string) { + fromStr, toStr := from, to + if t, err := time.Parse(time.RFC3339, from); err == nil { + fromStr = t.Format("2006-01-02 15:04") + } + if t, err := time.Parse(time.RFC3339, to); err == nil { + toStr = t.Format("2006-01-02 15:04") + } + return fromStr, toStr +} + +func truncate(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n-1] + "…" +} |
