Skip to content

Commit 68ac97a

Browse files
committed
enhanced pagination experience for users: {--page --per-page} flags introduced.
1 parent 167f890 commit 68ac97a

File tree

5 files changed

+193
-61
lines changed

5 files changed

+193
-61
lines changed

cmd/app.go

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"fmt"
45
"strings"
56

67
"github.com/onkernel/cli/pkg/util"
@@ -41,6 +42,8 @@ func init() {
4142
appListCmd.Flags().String("name", "", "Filter by application name")
4243
appListCmd.Flags().String("version", "", "Filter by version label")
4344
appListCmd.Flags().Int("limit", 20, "Max apps to return (default 20)")
45+
appListCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)")
46+
appListCmd.Flags().Int("page", 1, "Page number (1-based)")
4447

4548
// Limit rows returned for app history (0 = all)
4649
appHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)")
@@ -51,6 +54,24 @@ func runAppList(cmd *cobra.Command, args []string) error {
5154
appName, _ := cmd.Flags().GetString("name")
5255
version, _ := cmd.Flags().GetString("version")
5356
lim, _ := cmd.Flags().GetInt("limit")
57+
perPage, _ := cmd.Flags().GetInt("per-page")
58+
page, _ := cmd.Flags().GetInt("page")
59+
60+
// Determine pagination inputs: prefer page/per-page if provided; else map legacy --limit
61+
usePager := cmd.Flags().Changed("per-page") || cmd.Flags().Changed("page")
62+
if !usePager && cmd.Flags().Changed("limit") {
63+
if lim < 0 {
64+
lim = 0
65+
}
66+
perPage = lim
67+
page = 1
68+
}
69+
if perPage <= 0 {
70+
perPage = 20
71+
}
72+
if page <= 0 {
73+
page = 1
74+
}
5475

5576
pterm.Debug.Println("Fetching deployed applications...")
5677

@@ -61,25 +82,37 @@ func runAppList(cmd *cobra.Command, args []string) error {
6182
if version != "" {
6283
params.Version = kernel.Opt(version)
6384
}
85+
// Apply server-side pagination (request one extra to detect hasMore)
86+
params.Limit = kernel.Opt(int64(perPage + 1))
87+
params.Offset = kernel.Opt(int64((page - 1) * perPage))
6488

6589
apps, err := client.Apps.List(cmd.Context(), params)
6690
if err != nil {
6791
pterm.Error.Printf("Failed to list applications: %v\n", err)
6892
return nil
6993
}
7094

71-
if apps == nil || len(*apps) == 0 {
95+
if apps == nil || len(apps.Items) == 0 {
7296
pterm.Info.Println("No applications found")
7397
return nil
7498
}
7599

100+
// Determine hasMore using +1 item trick and keep only perPage items for display
101+
items := apps.Items
102+
hasMore := false
103+
if len(items) > perPage {
104+
hasMore = true
105+
items = items[:perPage]
106+
}
107+
itemsThisPage := len(items)
108+
76109
// Prepare table data
77110
tableData := pterm.TableData{
78111
{"App Name", "Version", "App Version ID", "Region", "Actions", "Env Vars"},
79112
}
80113

81114
rows := 0
82-
for _, app := range *apps {
115+
for _, app := range items {
83116
// Format env vars
84117
envVarsStr := "-"
85118
if len(app.EnvVars) > 0 {
@@ -102,12 +135,57 @@ func runAppList(cmd *cobra.Command, args []string) error {
102135
envVarsStr,
103136
})
104137
rows++
105-
if lim > 0 && rows >= lim {
106-
break
107-
}
108138
}
109139

110140
PrintTableNoPad(tableData, true)
141+
142+
// Footer with pagination details and next command suggestion
143+
fmt.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no"))
144+
if hasMore {
145+
nextPage := page + 1
146+
nextCmd := fmt.Sprintf("kernel app list --page %d --per-page %d", nextPage, perPage)
147+
if appName != "" {
148+
nextCmd += fmt.Sprintf(" --name %s", appName)
149+
}
150+
if version != "" {
151+
nextCmd += fmt.Sprintf(" --version %s", version)
152+
}
153+
fmt.Printf("Next: %s\n", nextCmd)
154+
}
155+
// Concise notes when user-specified per-page/limit/page are outside API-allowed range
156+
if cmd.Flags().Changed("per-page") {
157+
if v, _ := cmd.Flags().GetInt("per-page"); v > 100 {
158+
pterm.Warning.Printfln("Requested --per-page %d; capped to 100.", v)
159+
} else if v < 1 {
160+
if cmd.Flags().Changed("page") {
161+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
162+
pterm.Warning.Println("Requested --per-page <1 and --page <1; using per-page=20, page=1.")
163+
} else {
164+
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
165+
}
166+
} else {
167+
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
168+
}
169+
}
170+
} else if !usePager && cmd.Flags().Changed("limit") {
171+
if lim > 100 {
172+
pterm.Warning.Printfln("Requested --limit %d; capped to 100.", lim)
173+
} else if lim < 1 {
174+
if cmd.Flags().Changed("page") {
175+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
176+
pterm.Warning.Println("Requested --limit <1 and --page <1; using per-page=20, page=1.")
177+
} else {
178+
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
179+
}
180+
} else {
181+
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
182+
}
183+
}
184+
} else if cmd.Flags().Changed("page") {
185+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
186+
pterm.Warning.Println("Requested --page <1; using page=1.")
187+
}
188+
}
111189
return nil
112190
}
113191

cmd/deploy.go

Lines changed: 104 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
kernel "github.com/onkernel/kernel-go-sdk"
2020
"github.com/onkernel/kernel-go-sdk/option"
2121
"github.com/pterm/pterm"
22+
"github.com/samber/lo"
2223
"github.com/spf13/cobra"
2324
)
2425

@@ -64,6 +65,8 @@ func init() {
6465
deployCmd.AddCommand(deployLogsCmd)
6566

6667
deployHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)")
68+
deployHistoryCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)")
69+
deployHistoryCmd.Flags().Int("page", 1, "Page number (1-based)")
6770
deployCmd.AddCommand(deployHistoryCmd)
6871

6972
// Flags for GitHub deploy
@@ -354,65 +357,116 @@ func runDeployHistory(cmd *cobra.Command, args []string) error {
354357
client := getKernelClient(cmd)
355358

356359
lim, _ := cmd.Flags().GetInt("limit")
360+
perPage, _ := cmd.Flags().GetInt("per-page")
361+
page, _ := cmd.Flags().GetInt("page")
362+
363+
// Prefer page/per-page when provided; map legacy --limit otherwise
364+
usePager := cmd.Flags().Changed("per-page") || cmd.Flags().Changed("page")
365+
if !usePager && cmd.Flags().Changed("limit") {
366+
if lim < 0 {
367+
lim = 0
368+
}
369+
perPage = lim
370+
page = 1
371+
}
372+
if perPage <= 0 {
373+
perPage = 20
374+
}
375+
if page <= 0 {
376+
page = 1
377+
}
357378

358-
var appNames []string
379+
// Build server-side paginated request
380+
var appNameFilter string
359381
if len(args) == 1 {
360-
appNames = []string{args[0]}
361-
} else {
362-
apps, err := client.Apps.List(cmd.Context(), kernel.AppListParams{})
363-
if err != nil {
364-
pterm.Error.Printf("Failed to list applications: %v\n", err)
365-
return nil
366-
}
367-
for _, a := range *apps {
368-
appNames = append(appNames, a.AppName)
369-
}
370-
// de-duplicate app names
371-
seenApps := map[string]struct{}{}
372-
uniq := make([]string, 0, len(appNames))
373-
for _, n := range appNames {
374-
if _, ok := seenApps[n]; ok {
375-
continue
376-
}
377-
seenApps[n] = struct{}{}
378-
uniq = append(uniq, n)
379-
}
380-
appNames = uniq
382+
appNameFilter = strings.TrimSpace(args[0])
381383
}
382384

383-
rows := 0
384-
table := pterm.TableData{{"Deployment ID", "Created At", "Region", "Status", "Entrypoint", "Reason"}}
385-
AppsLoop:
386-
for _, appName := range appNames {
387-
params := kernel.DeploymentListParams{AppName: kernel.Opt(appName)}
388-
pterm.Debug.Printf("Listing deployments for app '%s'...\n", appName)
389-
deployments, err := client.Deployments.List(cmd.Context(), params)
390-
if err != nil {
391-
pterm.Error.Printf("Failed to list deployments for '%s': %v\n", appName, err)
392-
continue
393-
}
394-
for _, dep := range deployments.Items {
395-
created := util.FormatLocal(dep.CreatedAt)
396-
status := string(dep.Status)
397-
table = append(table, []string{
398-
dep.ID,
399-
created,
400-
string(dep.Region),
401-
status,
402-
dep.EntrypointRelPath,
403-
dep.StatusReason,
404-
})
405-
rows++
406-
if lim > 0 && rows >= lim {
407-
break AppsLoop
408-
}
409-
}
385+
params := kernel.DeploymentListParams{}
386+
if appNameFilter != "" {
387+
params.AppName = kernel.Opt(appNameFilter)
388+
}
389+
// Request one extra item to detect hasMore
390+
params.Limit = kernel.Opt(int64(perPage + 1))
391+
params.Offset = kernel.Opt(int64((page - 1) * perPage))
392+
393+
pterm.Debug.Println("Fetching deployments...")
394+
deployments, err := client.Deployments.List(cmd.Context(), params)
395+
if err != nil {
396+
pterm.Error.Printf("Failed to list deployments: %v\n", err)
397+
return nil
410398
}
411-
if len(table) == 1 {
399+
if deployments == nil || len(deployments.Items) == 0 {
412400
pterm.Info.Println("No deployments found")
413401
return nil
414402
}
403+
404+
items := deployments.Items
405+
hasMore := false
406+
if len(items) > perPage {
407+
hasMore = true
408+
items = items[:perPage]
409+
}
410+
itemsThisPage := len(items)
411+
412+
table := pterm.TableData{{"Deployment ID", "Created At", "Region", "Status", "Entrypoint", "Reason"}}
413+
for _, dep := range items {
414+
created := util.FormatLocal(dep.CreatedAt)
415+
status := string(dep.Status)
416+
table = append(table, []string{
417+
dep.ID,
418+
created,
419+
string(dep.Region),
420+
status,
421+
dep.EntrypointRelPath,
422+
dep.StatusReason,
423+
})
424+
}
415425
pterm.DefaultTable.WithHasHeader().WithData(table).Render()
426+
427+
fmt.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no"))
428+
if hasMore {
429+
nextPage := page + 1
430+
nextCmd := fmt.Sprintf("kernel deploy history --page %d --per-page %d", nextPage, perPage)
431+
if appNameFilter != "" {
432+
nextCmd = fmt.Sprintf("kernel deploy history %s --page %d --per-page %d", appNameFilter, nextPage, perPage)
433+
}
434+
fmt.Printf("Next: %s\n", nextCmd)
435+
}
436+
// Concise notes when user-specified per-page/limit/page are outside API-allowed range
437+
if cmd.Flags().Changed("per-page") {
438+
if v, _ := cmd.Flags().GetInt("per-page"); v > 100 {
439+
pterm.Warning.Printfln("Requested --per-page %d; capped to 100.", v)
440+
} else if v < 1 {
441+
if cmd.Flags().Changed("page") {
442+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
443+
pterm.Warning.Println("Requested --per-page <1 and --page <1; using per-page=20, page=1.")
444+
} else {
445+
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
446+
}
447+
} else {
448+
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
449+
}
450+
}
451+
} else if !usePager && cmd.Flags().Changed("limit") {
452+
if lim > 100 {
453+
pterm.Warning.Printfln("Requested --limit %d; capped to 100.", lim)
454+
} else if lim < 1 {
455+
if cmd.Flags().Changed("page") {
456+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
457+
pterm.Warning.Println("Requested --limit <1 and --page <1; using per-page=20, page=1.")
458+
} else {
459+
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
460+
}
461+
} else {
462+
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
463+
}
464+
}
465+
} else if cmd.Flags().Changed("page") {
466+
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
467+
pterm.Warning.Println("Requested --page <1; using page=1.")
468+
}
469+
}
416470
return nil
417471
}
418472

cmd/logs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,13 @@ func runLogs(cmd *cobra.Command, args []string) error {
140140
if err != nil {
141141
return fmt.Errorf("failed to list apps: %w", err)
142142
}
143-
if apps == nil || len(*apps) == 0 {
143+
if apps == nil || len(apps.Items) == 0 {
144144
return fmt.Errorf("app \"%s\" not found", appName)
145145
}
146-
if len(*apps) > 1 {
146+
if len(apps.Items) > 1 {
147147
return fmt.Errorf("multiple apps found for \"%s\", please specify a version", appName)
148148
}
149-
app := (*apps)[0]
149+
app := apps.Items[0]
150150

151151
pterm.Info.Printf("Streaming logs for app \"%s\" (version: %s, id: %s)...\n", appName, version, app.ID)
152152
if follow {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/charmbracelet/fang v0.2.0
99
github.com/golang-jwt/jwt/v5 v5.2.2
1010
github.com/joho/godotenv v1.5.1
11-
github.com/onkernel/kernel-go-sdk v0.17.0
11+
github.com/onkernel/kernel-go-sdk v0.18.0
1212
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1313
github.com/pterm/pterm v0.12.80
1414
github.com/samber/lo v1.51.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe
9191
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
9292
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
9393
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
94-
github.com/onkernel/kernel-go-sdk v0.17.0 h1:3q7hrfiLTJbUwcJTtPhdnNIXUI4/TUnoklPjUGBoeas=
95-
github.com/onkernel/kernel-go-sdk v0.17.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
94+
github.com/onkernel/kernel-go-sdk v0.18.0 h1:hlBqxL2sEUto6h449b93C0YkAQeRdxrhn5cAScbhjaQ=
95+
github.com/onkernel/kernel-go-sdk v0.18.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
9696
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
9797
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
9898
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

0 commit comments

Comments
 (0)