mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-24 05:10:43 +00:00
- Optimized terminal output style and configuration management using libraries such as `lipgloss` and `go-json` - Introduced the `ui` package to unify the display logic of colors, tables, and prompts - Updated the README document structure and installation script links to improve readability and internationalization support - Improved the interaction flow and log display effects of the daemon startup and attach commands - Fixed some command parameter parsing issues, improving program robustness and user onboarding experience
188 lines
4.2 KiB
Go
188 lines
4.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"drip/internal/client/cli/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/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,
|
|
}
|
|
|
|
func init() {
|
|
listCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Interactive mode for attach/stop")
|
|
rootCmd.AddCommand(listCmd)
|
|
}
|
|
|
|
func runList(cmd *cobra.Command, args []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 || shouldPromptForAction() {
|
|
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 shouldPromptForAction() bool {
|
|
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|