Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 88 additions & 3 deletions cmd/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"fmt"
"strings"

"github.com/onkernel/cli/pkg/util"
Expand Down Expand Up @@ -40,15 +41,37 @@ func init() {
// Add optional filters for list
appListCmd.Flags().String("name", "", "Filter by application name")
appListCmd.Flags().String("version", "", "Filter by version label")
appListCmd.Flags().Int("limit", 20, "Max apps to return (default 20)")
appListCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)")
appListCmd.Flags().Int("page", 1, "Page number (1-based)")

// Limit rows returned for app history (0 = all)
appHistoryCmd.Flags().Int("limit", 100, "Max deployments to return (default 100)")
appHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)")
}

func runAppList(cmd *cobra.Command, args []string) error {
client := getKernelClient(cmd)
appName, _ := cmd.Flags().GetString("name")
version, _ := cmd.Flags().GetString("version")
lim, _ := cmd.Flags().GetInt("limit")
perPage, _ := cmd.Flags().GetInt("per-page")
page, _ := cmd.Flags().GetInt("page")

// Determine pagination inputs: prefer page/per-page if provided; else map legacy --limit
usePager := cmd.Flags().Changed("per-page") || cmd.Flags().Changed("page")
if !usePager && cmd.Flags().Changed("limit") {
if lim < 0 {
lim = 0
}
perPage = lim
page = 1
}
if perPage <= 0 {
perPage = 20
}
if page <= 0 {
page = 1
}

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

Expand All @@ -59,24 +82,37 @@ func runAppList(cmd *cobra.Command, args []string) error {
if version != "" {
params.Version = kernel.Opt(version)
}
// Apply server-side pagination (request one extra to detect hasMore)
params.Limit = kernel.Opt(int64(perPage + 1))
params.Offset = kernel.Opt(int64((page - 1) * perPage))

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

if apps == nil || len(*apps) == 0 {
if apps == nil || len(apps.Items) == 0 {
pterm.Info.Println("No applications found")
return nil
}

// Determine hasMore using +1 item trick and keep only perPage items for display
items := apps.Items
hasMore := false
if len(items) > perPage {
hasMore = true
items = items[:perPage]
}
itemsThisPage := len(items)

// Prepare table data
tableData := pterm.TableData{
{"App Name", "Version", "App Version ID", "Region", "Actions", "Env Vars"},
}

for _, app := range *apps {
rows := 0
for _, app := range items {
// Format env vars
envVarsStr := "-"
if len(app.EnvVars) > 0 {
Expand All @@ -98,9 +134,58 @@ func runAppList(cmd *cobra.Command, args []string) error {
actionsStr,
envVarsStr,
})
rows++
}

PrintTableNoPad(tableData, true)

// Footer with pagination details and next command suggestion
fmt.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no"))
if hasMore {
nextPage := page + 1
nextCmd := fmt.Sprintf("kernel app list --page %d --per-page %d", nextPage, perPage)
if appName != "" {
nextCmd += fmt.Sprintf(" --name %s", appName)
}
if version != "" {
nextCmd += fmt.Sprintf(" --version %s", version)
}
fmt.Printf("Next: %s\n", nextCmd)
}
// Concise notes when user-specified per-page/limit/page are outside API-allowed range
if cmd.Flags().Changed("per-page") {
if v, _ := cmd.Flags().GetInt("per-page"); v > 100 {
pterm.Warning.Printfln("Requested --per-page %d; capped to 100.", v)
} else if v < 1 {
if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --per-page <1 and --page <1; using per-page=20, page=1.")
} else {
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
}
} else {
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
}
}
} else if !usePager && cmd.Flags().Changed("limit") {
if lim > 100 {
pterm.Warning.Printfln("Requested --limit %d; capped to 100.", lim)
} else if lim < 1 {
if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --limit <1 and --page <1; using per-page=20, page=1.")
} else {
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
}
} else {
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
}
}
} else if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --page <1; using page=1.")
}
}
return nil
}

Expand Down
156 changes: 105 additions & 51 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
kernel "github.com/onkernel/kernel-go-sdk"
"github.com/onkernel/kernel-go-sdk/option"
"github.com/pterm/pterm"
"github.com/samber/lo"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -63,7 +64,9 @@ func init() {
deployLogsCmd.Flags().BoolP("with-timestamps", "t", false, "Include timestamps in each log line")
deployCmd.AddCommand(deployLogsCmd)

deployHistoryCmd.Flags().Int("limit", 100, "Max deployments to return (default 100)")
deployHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)")
deployHistoryCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)")
deployHistoryCmd.Flags().Int("page", 1, "Page number (1-based)")
deployCmd.AddCommand(deployHistoryCmd)

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

lim, _ := cmd.Flags().GetInt("limit")
perPage, _ := cmd.Flags().GetInt("per-page")
page, _ := cmd.Flags().GetInt("page")

// Prefer page/per-page when provided; map legacy --limit otherwise
usePager := cmd.Flags().Changed("per-page") || cmd.Flags().Changed("page")
if !usePager && cmd.Flags().Changed("limit") {
if lim < 0 {
lim = 0
}
perPage = lim
page = 1
}
if perPage <= 0 {
perPage = 20
}
if page <= 0 {
page = 1
}

var appNames []string
// Build server-side paginated request
var appNameFilter string
if len(args) == 1 {
appNames = []string{args[0]}
} else {
apps, err := client.Apps.List(cmd.Context(), kernel.AppListParams{})
if err != nil {
pterm.Error.Printf("Failed to list applications: %v\n", err)
return nil
}
for _, a := range *apps {
appNames = append(appNames, a.AppName)
}
// de-duplicate app names
seenApps := map[string]struct{}{}
uniq := make([]string, 0, len(appNames))
for _, n := range appNames {
if _, ok := seenApps[n]; ok {
continue
}
seenApps[n] = struct{}{}
uniq = append(uniq, n)
}
appNames = uniq
appNameFilter = strings.TrimSpace(args[0])
}

rows := 0
table := pterm.TableData{{"Deployment ID", "Created At", "Region", "Status", "Entrypoint", "Reason"}}
AppsLoop:
for _, appName := range appNames {
params := kernel.DeploymentListParams{AppName: kernel.Opt(appName)}
pterm.Debug.Printf("Listing deployments for app '%s'...\n", appName)
deployments, err := client.Deployments.List(cmd.Context(), params)
if err != nil {
pterm.Error.Printf("Failed to list deployments for '%s': %v\n", appName, err)
continue
}
for _, dep := range deployments.Items {
created := util.FormatLocal(dep.CreatedAt)
status := string(dep.Status)
table = append(table, []string{
dep.ID,
created,
string(dep.Region),
status,
dep.EntrypointRelPath,
dep.StatusReason,
})
rows++
if lim > 0 && rows >= lim {
break AppsLoop
}
}
params := kernel.DeploymentListParams{}
if appNameFilter != "" {
params.AppName = kernel.Opt(appNameFilter)
}
// Request one extra item to detect hasMore
params.Limit = kernel.Opt(int64(perPage + 1))
params.Offset = kernel.Opt(int64((page - 1) * perPage))

pterm.Debug.Println("Fetching deployments...")
deployments, err := client.Deployments.List(cmd.Context(), params)
if err != nil {
pterm.Error.Printf("Failed to list deployments: %v\n", err)
return nil
}
if len(table) == 1 {
if deployments == nil || len(deployments.Items) == 0 {
pterm.Info.Println("No deployments found")
return nil
}

items := deployments.Items
hasMore := false
if len(items) > perPage {
hasMore = true
items = items[:perPage]
}
itemsThisPage := len(items)

table := pterm.TableData{{"Deployment ID", "Created At", "Region", "Status", "Entrypoint", "Reason"}}
for _, dep := range items {
created := util.FormatLocal(dep.CreatedAt)
status := string(dep.Status)
table = append(table, []string{
dep.ID,
created,
string(dep.Region),
status,
dep.EntrypointRelPath,
dep.StatusReason,
})
}
pterm.DefaultTable.WithHasHeader().WithData(table).Render()

fmt.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no"))
if hasMore {
nextPage := page + 1
nextCmd := fmt.Sprintf("kernel deploy history --page %d --per-page %d", nextPage, perPage)
if appNameFilter != "" {
nextCmd = fmt.Sprintf("kernel deploy history %s --page %d --per-page %d", appNameFilter, nextPage, perPage)
}
fmt.Printf("Next: %s\n", nextCmd)
}
// Concise notes when user-specified per-page/limit/page are outside API-allowed range
if cmd.Flags().Changed("per-page") {
if v, _ := cmd.Flags().GetInt("per-page"); v > 100 {
pterm.Warning.Printfln("Requested --per-page %d; capped to 100.", v)
} else if v < 1 {
if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --per-page <1 and --page <1; using per-page=20, page=1.")
} else {
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
}
} else {
pterm.Warning.Println("Requested --per-page <1; using per-page=20.")
}
}
} else if !usePager && cmd.Flags().Changed("limit") {
if lim > 100 {
pterm.Warning.Printfln("Requested --limit %d; capped to 100.", lim)
} else if lim < 1 {
if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --limit <1 and --page <1; using per-page=20, page=1.")
} else {
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
}
} else {
pterm.Warning.Println("Requested --limit <1; using per-page=20.")
}
}
} else if cmd.Flags().Changed("page") {
if p, _ := cmd.Flags().GetInt("page"); p < 1 {
pterm.Warning.Println("Requested --page <1; using page=1.")
}
}
return nil
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ func runLogs(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("failed to list apps: %w", err)
}
if apps == nil || len(*apps) == 0 {
if apps == nil || len(apps.Items) == 0 {
return fmt.Errorf("app \"%s\" not found", appName)
}
if len(*apps) > 1 {
if len(apps.Items) > 1 {
return fmt.Errorf("multiple apps found for \"%s\", please specify a version", appName)
}
app := (*apps)[0]
app := apps.Items[0]

pterm.Info.Printf("Streaming logs for app \"%s\" (version: %s, id: %s)...\n", appName, version, app.ID)
if follow {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/charmbracelet/fang v0.2.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/joho/godotenv v1.5.1
github.com/onkernel/kernel-go-sdk v0.17.0
github.com/onkernel/kernel-go-sdk v0.18.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pterm/pterm v0.12.80
github.com/samber/lo v1.51.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/onkernel/kernel-go-sdk v0.17.0 h1:3q7hrfiLTJbUwcJTtPhdnNIXUI4/TUnoklPjUGBoeas=
github.com/onkernel/kernel-go-sdk v0.17.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
github.com/onkernel/kernel-go-sdk v0.18.0 h1:hlBqxL2sEUto6h449b93C0YkAQeRdxrhn5cAScbhjaQ=
github.com/onkernel/kernel-go-sdk v0.18.0/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down