Files
drip/internal/client/cli/list.go
zhiqing 307cf8e6cc feat: Add Bearer Token authentication support and optimize code structure
- Add Bearer Token authentication, supporting tunnel access control via the --auth-bearer parameter
- Refactor large modules into smaller, more focused components to improve code maintainability
- Update dependency versions, including golang.org/x/crypto, golang.org/x/net, etc.
- Add SilenceUsage and SilenceErrors configuration for all CLI commands
- Modify connector configuration structure to support the new authentication method
- Update recent change log in README with new feature descriptions

BREAKING CHANGE: Authentication via Bearer Token is now supported, requiring the new --auth-bearer parameter
2026-01-29 14:40:53 +08:00

181 lines
4.0 KiB
Go

package cli
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
"drip/internal/shared/ui"
"github.com/spf13/cobra"
)
var interactiveMode bool
var listCmd = &cobra.Command{
Use: "list",
Short: "List all running background tunnels",
Long: `List all running background tunnels.
Example:
drip list Show all running tunnels
drip list -i Interactive mode (select to attach/stop)
This command shows:
- Tunnel type (HTTP/HTTPS/TCP)
- Local port being tunneled
- Public URL
- Process ID (PID)
- Uptime
In interactive mode, you can select a tunnel to:
- Attach: View real-time logs
- Stop: Terminate the tunnel`,
Aliases: []string{"ls", "ps", "status"},
RunE: runList,
SilenceUsage: true,
SilenceErrors: true,
}
func init() {
listCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Interactive mode for attach/stop")
rootCmd.AddCommand(listCmd)
}
func runList(_ *cobra.Command, _ []string) error {
CleanupStaleDaemons()
daemons, err := ListAllDaemons()
if err != nil {
return fmt.Errorf("failed to list daemons: %w", err)
}
if len(daemons) == 0 {
fmt.Println()
fmt.Println(ui.Info(
"No Running Tunnels",
"",
ui.Muted("Start a tunnel in background with:"),
"",
ui.Cyan(" drip http 3000 -d"),
ui.Cyan(" drip tcp 5432 -d"),
))
return nil
}
table := ui.NewTable([]string{"#", "TYPE", "PORT", "URL", "PID", "UPTIME"}).
WithTitle("Running Tunnels")
idx := 1
for _, d := range daemons {
if !IsProcessRunning(d.PID) {
RemoveDaemonInfo(d.Type, d.Port)
continue
}
uptime := time.Since(d.StartTime)
var typeStr string
if d.Type == "http" {
typeStr = ui.Highlight("HTTP")
} else if d.Type == "https" {
typeStr = ui.Highlight("HTTPS")
} else {
typeStr = ui.Cyan("TCP")
}
table.AddRow([]string{
ui.Highlight(fmt.Sprintf("%d", idx)),
typeStr,
fmt.Sprintf("%d", d.Port),
ui.URL(d.URL),
ui.Muted(fmt.Sprintf("%d", d.PID)),
FormatDuration(uptime),
})
idx++
}
fmt.Print(table.Render())
if interactiveMode {
return runInteractiveList(daemons)
}
fmt.Println(ui.Muted("Commands:"))
fmt.Println(ui.RenderList([]string{
ui.Cyan("drip list -i") + ui.Muted(" Interactive mode"),
ui.Cyan("drip attach http 3000") + ui.Muted(" Attach to tunnel (view logs)"),
ui.Cyan("drip stop http 3000") + ui.Muted(" Stop tunnel"),
ui.Cyan("drip stop all") + ui.Muted(" Stop all tunnels"),
}))
return nil
}
func runInteractiveList(daemons []*DaemonInfo) error {
var runningDaemons []*DaemonInfo
for _, d := range daemons {
if IsProcessRunning(d.PID) {
runningDaemons = append(runningDaemons, d)
} else {
RemoveDaemonInfo(d.Type, d.Port)
}
}
if len(runningDaemons) == 0 {
return nil
}
fmt.Println()
fmt.Print(ui.Muted("Select a tunnel (number) or 'q' to quit: "))
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
input = strings.TrimSpace(input)
if input == "" || input == "q" || input == "Q" {
return nil
}
selection, err := strconv.Atoi(input)
if err != nil || selection < 1 || selection > len(runningDaemons) {
return fmt.Errorf("invalid selection: %s", input)
}
selectedDaemon := runningDaemons[selection-1]
fmt.Println()
fmt.Println(ui.Info(
fmt.Sprintf("Selected: %s tunnel on port %d", strings.ToUpper(selectedDaemon.Type), selectedDaemon.Port),
"",
ui.Muted("What would you like to do?"),
"",
ui.Cyan(" 1.")+" Attach (view logs)",
ui.Cyan(" 2.")+" Stop tunnel",
ui.Muted(" q.")+" Cancel",
))
fmt.Print(ui.Muted("Choose an action: "))
actionInput, err := reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
actionInput = strings.TrimSpace(actionInput)
switch actionInput {
case "1":
return attachToDaemon(selectedDaemon)
case "2":
return stopDaemon(selectedDaemon.Type, selectedDaemon.Port)
case "q", "Q", "":
return nil
default:
return fmt.Errorf("invalid action: %s", actionInput)
}
}