Skip to content

Commit 3cd107e

Browse files
committed
Add support for cursor pagination to show audit logs command
1 parent 6f49abb commit 3cd107e

2 files changed

Lines changed: 81 additions & 75 deletions

File tree

internal/cmd/org.go

Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cmd
22

33
import (
4+
"bufio"
45
"fmt"
6+
"os"
57
"strconv"
68
"time"
79

@@ -24,9 +26,9 @@ func init() {
2426
orgCmd.AddCommand(invitesCmd)
2527
orgCmd.AddCommand(auditLogsCmd)
2628
auditLogsCmd.AddCommand(auditLogsListCmd)
27-
auditLogsListCmd.Flags().IntP("page", "p", 1, "Page number")
28-
auditLogsListCmd.Flags().IntP("limit", "s", 25, "Number of logs per page")
29-
auditLogsListCmd.Flags().BoolP("verbose", "v", false, "Show detailed audit log information")
29+
auditLogsListCmd.Flags().BoolP("verbose", "v", false, "Show full log data")
30+
auditLogsListCmd.Flags().IntP("limit", "l", 50, "Items per page")
31+
auditLogsListCmd.Flags().Bool("all", false, "Fetch all pages without stopping")
3032
membersCmd.AddCommand(membersListCmd)
3133
membersCmd.AddCommand(membersAddCmd)
3234
membersCmd.AddCommand(membersRemoveCmd)
@@ -109,19 +111,16 @@ var auditLogsListCmd = &cobra.Command{
109111
return err
110112
}
111113

112-
page, err := cmd.Flags().GetInt("page")
113-
if err != nil {
114-
return err
114+
limit, _ := cmd.Flags().GetInt("limit")
115+
all, _ := cmd.Flags().GetBool("all")
116+
verbose, _ := cmd.Flags().GetBool("verbose")
117+
if limit != 0 && all {
118+
return fmt.Errorf("cannot use --all together with --limit")
115119
}
116-
117-
limit, err := cmd.Flags().GetInt("limit")
118-
if err != nil {
119-
return err
120+
if limit <= 0 {
121+
limit = 50
120122
}
121123

122-
s := prompt.Spinner("Fetching audit logs")
123-
defer s.Stop()
124-
125124
settingsObj, err := settings.ReadSettings()
126125
if err != nil {
127126
return err
@@ -130,56 +129,7 @@ var auditLogsListCmd = &cobra.Command{
130129
if org == "" {
131130
org = settingsObj.GetUsername()
132131
}
133-
134-
auditLogs, err := client.Organizations.AuditLogs(org, page, limit)
135-
if err != nil {
136-
return err
137-
}
138-
139-
if len(auditLogs.AuditLogs) == 0 {
140-
fmt.Println("No audit logs found.")
141-
return nil
142-
}
143-
144-
fmt.Printf("Showing %d of %d audit logs (page %d of %d):\n\n",
145-
len(auditLogs.AuditLogs),
146-
auditLogs.Pagination.TotalRows,
147-
auditLogs.Pagination.Page,
148-
auditLogs.Pagination.TotalPages)
149-
150-
verbose, _ := cmd.Flags().GetBool("verbose")
151-
152-
if verbose {
153-
154-
for _, log := range auditLogs.AuditLogs {
155-
timestamp := internal.Emph(log.CreatedAt)
156-
author := internal.Emph(log.Author)
157-
origin := internal.Emph(log.Origin)
158-
code := internal.Emph(log.Code)
159-
160-
fmt.Printf("%s: %s via %s performed %s\n", timestamp, author, origin, code)
161-
162-
if len(log.Data) > 0 {
163-
fmt.Println(" Data:")
164-
for key, value := range log.Data {
165-
fmt.Printf(" %s: %v\n", key, value)
166-
}
167-
}
168-
fmt.Println()
169-
}
170-
} else {
171-
172-
data := make([][]string, 0, len(auditLogs.AuditLogs))
173-
for _, log := range auditLogs.AuditLogs {
174-
timestamp := log.CreatedAt
175-
176-
if t, err := time.Parse(time.RFC3339, log.CreatedAt); err == nil {
177-
timestamp = t.Format("2006-01-02 15:04:05")
178-
}
179-
data = append(data, []string{timestamp, log.Author, log.Code, log.Origin})
180-
}
181-
printTable([]string{"date", "author", "code", "origin"}, data)
182-
}
132+
displayAuditLog(limit, all, verbose, client, org)
183133

184134
return nil
185135
},
@@ -633,3 +583,63 @@ func listOrganizations(client *turso.Client, fresh ...bool) ([]turso.Organizatio
633583
setOrgsCache(orgs)
634584
return orgs, nil
635585
}
586+
587+
func displayAuditLog(limit int, all bool, verbose bool, client *turso.Client, org string) error {
588+
cursor := ""
589+
page := 1
590+
for {
591+
s := prompt.Spinner(fmt.Sprintf("Fetching page %d", page))
592+
resp, err := client.Organizations.AuditLogs(org, cursor, limit)
593+
s.Stop()
594+
if err != nil {
595+
return err
596+
}
597+
if len(resp.AuditLogs) == 0 {
598+
if page == 1 {
599+
fmt.Println("No audit logs found.")
600+
}
601+
break
602+
}
603+
604+
if verbose {
605+
for _, log := range resp.AuditLogs {
606+
timestamp := internal.Emph(log.CreatedAt)
607+
author := internal.Emph(log.Author)
608+
origin := internal.Emph(log.Origin)
609+
code := internal.Emph(log.Code)
610+
611+
fmt.Printf("%s: %s via %s performed %s\n", timestamp, author, origin, code)
612+
613+
if len(log.Data) > 0 {
614+
fmt.Println(" Data:")
615+
for key, value := range log.Data {
616+
fmt.Printf(" %s: %v\n", key, value)
617+
}
618+
}
619+
fmt.Println()
620+
}
621+
} else {
622+
rows := make([][]string, 0, len(resp.AuditLogs))
623+
for _, log := range resp.AuditLogs {
624+
t := log.CreatedAt
625+
if parsed, err := time.Parse(time.RFC3339, t); err == nil {
626+
t = parsed.Format("2006-01-02 15:04:05")
627+
}
628+
rows = append(rows, []string{t, log.Author, log.Code, log.Origin})
629+
}
630+
printTable([]string{"date", "author", "code", "origin"}, rows)
631+
}
632+
633+
if resp.Next == "" || len(resp.AuditLogs) < limit {
634+
break
635+
}
636+
637+
if !all {
638+
fmt.Printf("- more available (page %d) - Press Enter to continue or Ctrl+C to stop -\n", page)
639+
bufio.NewReader(os.Stdin).ReadBytes('\n')
640+
}
641+
cursor = resp.Next
642+
page++
643+
}
644+
return nil
645+
}

internal/turso/organizations.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8+
"net/url"
89

910
"github.com/tursodatabase/turso-cli/internal"
1011
"github.com/tursodatabase/turso-cli/internal/settings"
@@ -377,21 +378,16 @@ type AuditLog struct {
377378
IP string `json:"ip,omitempty"`
378379
}
379380

380-
type AuditLogsPagination struct {
381-
PageSize int `json:"page_size,omitempty"`
382-
Page int `json:"page,omitempty"`
383-
Sort string `json:"sort,omitempty"`
384-
TotalRows int `json:"total_rows,omitempty"`
385-
TotalPages int `json:"total_pages,omitempty"`
386-
}
387-
388381
type AuditLogsResponse struct {
389-
AuditLogs []AuditLog `json:"audit_logs"`
390-
Pagination AuditLogsPagination `json:"pagination"`
382+
AuditLogs []AuditLog `json:"audit_logs"`
383+
Next string `json:"next"`
391384
}
392385

393-
func (c *OrganizationsClient) AuditLogs(org string, page int, limit int) (AuditLogsResponse, error) {
394-
path := fmt.Sprintf("/v1/organizations/%s/audit-logs?page=%d&page_size=%d", org, page, limit)
386+
func (c *OrganizationsClient) AuditLogs(org, cursor string, limit int) (AuditLogsResponse, error) {
387+
path := fmt.Sprintf("/v1/organizations/%s/audit-logs?limit=%d", org, limit)
388+
if cursor != "" {
389+
path += "&cursor=" + url.QueryEscape(cursor)
390+
}
395391
r, err := c.client.Get(path, nil)
396392
if err != nil {
397393
return AuditLogsResponse{}, fmt.Errorf("failed to get audit logs: %w", err)

0 commit comments

Comments
 (0)